Polish website flow and add Questions story

This commit is contained in:
2026-08-24 12:26:54 -04:00
parent dcc1fff784
commit 33d4f19bf0
16 changed files with 385 additions and 232 deletions
+2 -2
View File
@@ -21,8 +21,8 @@ Open [http://localhost:3000](http://localhost:3000).
Public routes include: Public routes include:
- `/` — consumer-first portable-context story - `/` — consumer-first portable-context story, including Questions
- `/how-it-works` inputs, review loop, medication maintenance, and handoff - `/how-it-works` — review loop, medication maintenance, Questions, and handoff
- `/privacy` — plain-language privacy and patient-control commitments - `/privacy` — plain-language privacy and patient-control commitments
- `/access` — health-data-free access request - `/access` — health-data-free access request
+5 -5
View File
@@ -78,7 +78,7 @@ export default function AccessPage() {
</p> </p>
</div> </div>
<div className="glass-panel rounded-[2rem] p-7 sm:p-9"> <div className="surface-panel rounded-[2rem] p-7 sm:p-9">
<div className="bg-primary-light text-primary flex size-12 items-center justify-center rounded-xl"> <div className="bg-primary-light text-primary flex size-12 items-center justify-center rounded-xl">
<Mail className="size-6" aria-hidden="true" /> <Mail className="size-6" aria-hidden="true" />
</div> </div>
@@ -104,7 +104,7 @@ export default function AccessPage() {
</div> </div>
</section> </section>
<section className="brand-warm border-border-soft/70 border-y px-4 py-20 sm:px-6 lg:py-24"> <section className="section-wash px-4 py-20 sm:px-6 lg:py-24">
<div className="mx-auto max-w-6xl"> <div className="mx-auto max-w-6xl">
<SectionLabel>Made for real life</SectionLabel> <SectionLabel>Made for real life</SectionLabel>
<h2 className="text-foreground mt-4 max-w-3xl font-serif text-4xl font-semibold tracking-tight sm:text-5xl"> <h2 className="text-foreground mt-4 max-w-3xl font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
@@ -118,7 +118,7 @@ export default function AccessPage() {
return ( return (
<article <article
key={item.title} key={item.title}
className="glass-panel rounded-2xl p-6" className="surface-panel rounded-2xl p-6"
> >
<Icon className="text-primary size-6" aria-hidden="true" /> <Icon className="text-primary size-6" aria-hidden="true" />
<h3 className="text-foreground mt-5 font-serif text-2xl font-semibold"> <h3 className="text-foreground mt-5 font-serif text-2xl font-semibold">
@@ -147,7 +147,7 @@ export default function AccessPage() {
return ( return (
<article <article
key={item.title} key={item.title}
className="glass-panel rounded-2xl p-6" className="surface-panel rounded-2xl p-6"
> >
<Icon className="text-primary size-6" aria-hidden="true" /> <Icon className="text-primary size-6" aria-hidden="true" />
<h3 className="text-foreground mt-5 font-serif text-2xl font-semibold"> <h3 className="text-foreground mt-5 font-serif text-2xl font-semibold">
@@ -161,7 +161,7 @@ export default function AccessPage() {
})} })}
</div> </div>
<div className="brand-night mt-14 rounded-[2rem] p-7 text-white shadow-[0_28px_70px_rgba(7,20,21,0.2)] sm:p-10"> <div className="night-card mt-14 rounded-[2rem] p-7 text-white sm:p-10">
<p className="text-xs font-bold tracking-[0.14em] text-teal-100 uppercase"> <p className="text-xs font-bold tracking-[0.14em] text-teal-100 uppercase">
Keep your care team in the loop Keep your care team in the loop
</p> </p>
+54 -35
View File
@@ -1,16 +1,17 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { import {
ArrowRight, ArrowRight,
Camera, CalendarDays,
Check, Check,
FileText, ListChecks,
Keyboard, MessageCircleQuestion,
Mic, RotateCcw,
ShieldAlert, ShieldAlert,
} from "lucide-react"; } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { PhoneMockup } from "~/components/phone-mockup"; import { PhoneMockup } from "~/components/phone-mockup";
import { QuestionsPreview } from "~/components/questions-preview";
import { SectionLabel } from "~/components/section-label"; import { SectionLabel } from "~/components/section-label";
import { Badge } from "~/components/ui/badge"; import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
@@ -21,26 +22,26 @@ export const metadata: Metadata = {
"See how Medscribe brings visits, medication routines, and important context into one reviewable record.", "See how Medscribe brings visits, medication routines, and important context into one reviewable record.",
}; };
const inputs = [ const dailyRhythm = [
{ {
icon: Keyboard, icon: CalendarDays,
title: "Type or paste", title: "Start with the moment",
body: "Start from notes you already have and keep the original details easy to revisit.", body: "Give each visit a clear place so the details remain easy to find later.",
}, },
{ {
icon: Camera, icon: ListChecks,
title: "Import a screenshot", title: "Review what matters",
body: "Bring in a portal summary or prescription label, then correct extracted fields.", body: "Check the useful details and keep your own judgment in the loop.",
}, },
{ {
icon: FileText, icon: MessageCircleQuestion,
title: "Attach a document", title: "Keep the next question",
body: "Bring a photo or document into a guided review workflow.", body: "Save what you want to ask while it is still fresh in your mind.",
}, },
{ {
icon: Mic, icon: RotateCcw,
title: "Record with consent", title: "Return when needed",
body: "With permission, turn optional visit audio into notes you can review.", body: "Revisit the same picture before the next appointment or handoff.",
}, },
] as const; ] as const;
@@ -68,20 +69,20 @@ export default function HowItWorksPage() {
</div> </div>
</section> </section>
<section className="brand-warm border-border-soft/70 border-y px-4 py-18 sm:px-6 lg:py-22"> <section className="section-wash px-4 py-18 sm:px-6 lg:py-22">
<div className="mx-auto max-w-6xl"> <div className="mx-auto max-w-6xl">
<SectionLabel>Start where the information is</SectionLabel> <SectionLabel>Start with what matters</SectionLabel>
<h2 className="text-foreground mt-4 max-w-3xl font-serif text-4xl font-semibold tracking-tight sm:text-5xl"> <h2 className="text-foreground mt-4 max-w-3xl font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
Four ways in. One clear place to review. A clear rhythm from the moment to the next conversation.
</h2> </h2>
<div className="mt-10 grid gap-4 sm:grid-cols-2 lg:grid-cols-4"> <div className="mt-10 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
{inputs.map((item, index) => { {dailyRhythm.map((item, index) => {
const Icon = item.icon; const Icon = item.icon;
return ( return (
<article <article
key={item.title} key={item.title}
className="glass-panel rounded-2xl p-6" className="surface-panel rounded-2xl p-6"
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="bg-primary-light text-primary flex size-11 items-center justify-center rounded-xl"> <span className="bg-primary-light text-primary flex size-11 items-center justify-center rounded-xl">
@@ -101,14 +102,14 @@ export default function HowItWorksPage() {
); );
})} })}
</div> </div>
<div className="border-primary/20 bg-primary-light/45 mt-6 flex gap-3 rounded-2xl border p-5"> <div className="surface-panel mt-6 flex gap-3 rounded-2xl p-5">
<Check <Check
className="text-primary mt-0.5 size-5 shrink-0" className="text-primary mt-0.5 size-5 shrink-0"
aria-hidden="true" aria-hidden="true"
/> />
<p className="text-foreground text-sm leading-relaxed"> <p className="text-foreground text-sm leading-relaxed">
You review summaries and medication details before they become You review the details before they become part of the picture you
part of the record you carry. carry.
</p> </p>
</div> </div>
</div> </div>
@@ -126,13 +127,13 @@ export default function HowItWorksPage() {
The visit remains available beside the summary so you can check The visit remains available beside the summary so you can check
wording, follow-ups, and the details you want to remember. wording, follow-ups, and the details you want to remember.
</p> </p>
<p className="glass-panel text-muted-foreground mt-5 rounded-2xl p-5 text-sm leading-relaxed"> <p className="surface-panel text-muted-foreground mt-5 rounded-2xl p-5 text-sm leading-relaxed">
<strong className="text-foreground">Stay oriented:</strong> move <strong className="text-foreground">Stay oriented:</strong> move
between the transcript and summary whenever you want the fuller between the transcript and summary whenever you want the fuller
context. context.
</p> </p>
</div> </div>
<div className="bg-primary-light/45 justify-self-center rounded-[2.5rem] p-7"> <div className="section-wash justify-self-center rounded-[2.5rem] p-7">
<PhoneMockup <PhoneMockup
src="/screenshots/product-bezel-2026-08-23-complete/summary-reviewed-natural.webp" src="/screenshots/product-bezel-2026-08-23-complete/summary-reviewed-natural.webp"
darkSrc="/screenshots/product-bezel-2026-08-23-complete/summary-reviewed-natural-dark.webp" darkSrc="/screenshots/product-bezel-2026-08-23-complete/summary-reviewed-natural-dark.webp"
@@ -146,7 +147,7 @@ export default function HowItWorksPage() {
</div> </div>
<div className="grid gap-12 lg:grid-cols-2 lg:items-center"> <div className="grid gap-12 lg:grid-cols-2 lg:items-center">
<div className="brand-warm justify-self-center rounded-[2.5rem] p-7 lg:order-1"> <div className="section-wash justify-self-center rounded-[2.5rem] p-7 lg:order-1">
<PhoneMockup <PhoneMockup
src="/screenshots/product-bezel-2026-08-23-complete/medication-detail-natural.webp" src="/screenshots/product-bezel-2026-08-23-complete/medication-detail-natural.webp"
darkSrc="/screenshots/product-bezel-2026-08-23-complete/medication-detail-natural-dark.webp" darkSrc="/screenshots/product-bezel-2026-08-23-complete/medication-detail-natural-dark.webp"
@@ -167,13 +168,31 @@ export default function HowItWorksPage() {
information together without hunting through separate notes. information together without hunting through separate notes.
</p> </p>
<p className="text-muted-foreground mt-5 text-sm leading-relaxed"> <p className="text-muted-foreground mt-5 text-sm leading-relaxed">
Medscribe records timing supplied by a label, pharmacist, or Keep medication timing easy to read and confirm changes with
prescribing team. It does not calculate or shift medication the care professional who gave you the instructions.
times.
</p> </p>
</div> </div>
</div> </div>
<div className="grid gap-12 lg:grid-cols-2 lg:items-center">
<div className="lg:order-2">
<SectionLabel>Keep the next question</SectionLabel>
<h2 className="text-foreground mt-4 font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
Save it while it is on your mind.
</h2>
<p className="text-muted-foreground mt-5 leading-relaxed">
Add questions as they come up between visits. If Ask reaches a
question that needs professional judgment, Medscribe can offer
to save it and suggest who may be helpful to ask.
</p>
<p className="text-muted-foreground mt-5 text-sm leading-relaxed">
Your list is ready when the appointment starts, so the most
important question does not disappear in the moment.
</p>
</div>
<QuestionsPreview className="justify-self-center lg:order-1 lg:w-full lg:max-w-md" />
</div>
<div className="grid gap-12 lg:grid-cols-2 lg:items-center"> <div className="grid gap-12 lg:grid-cols-2 lg:items-center">
<div> <div>
<SectionLabel>Prepare the handoff</SectionLabel> <SectionLabel>Prepare the handoff</SectionLabel>
@@ -190,7 +209,7 @@ export default function HowItWorksPage() {
it, and when the conversation happens. it, and when the conversation happens.
</p> </p>
</div> </div>
<div className="bg-primary-light/45 justify-self-center rounded-[2.5rem] p-7"> <div className="section-wash justify-self-center rounded-[2.5rem] p-7">
<PhoneMockup <PhoneMockup
src="/screenshots/product-bezel-2026-08-23-complete/know-me-natural.webp" src="/screenshots/product-bezel-2026-08-23-complete/know-me-natural.webp"
darkSrc="/screenshots/product-bezel-2026-08-23-complete/know-me-natural-dark.webp" darkSrc="/screenshots/product-bezel-2026-08-23-complete/know-me-natural-dark.webp"
@@ -205,7 +224,7 @@ export default function HowItWorksPage() {
</div> </div>
</section> </section>
<section className="brand-warm border-border-soft/70 border-t px-4 py-20 sm:px-6"> <section className="section-wash px-4 py-20 sm:px-6">
<div className="mx-auto grid max-w-6xl gap-12 lg:grid-cols-2"> <div className="mx-auto grid max-w-6xl gap-12 lg:grid-cols-2">
<div> <div>
<div className="bg-primary-light text-primary flex size-12 items-center justify-center rounded-xl"> <div className="bg-primary-light text-primary flex size-12 items-center justify-center rounded-xl">
@@ -222,7 +241,7 @@ export default function HowItWorksPage() {
{boundaries.map((item) => ( {boundaries.map((item) => (
<li <li
key={item} key={item}
className="glass-panel text-foreground flex gap-3 rounded-2xl p-4 text-sm leading-relaxed" className="surface-panel text-foreground flex gap-3 rounded-2xl p-4 text-sm leading-relaxed"
> >
<Check <Check
className="text-primary mt-0.5 size-5 shrink-0" className="text-primary mt-0.5 size-5 shrink-0"
@@ -236,7 +255,7 @@ export default function HowItWorksPage() {
</section> </section>
<section className="px-4 py-20 sm:px-6"> <section className="px-4 py-20 sm:px-6">
<div className="brand-night mx-auto flex max-w-6xl flex-col items-start justify-between gap-7 rounded-[2rem] px-7 py-10 text-white shadow-[0_28px_70px_rgba(7,20,21,0.2)] sm:px-10 md:flex-row md:items-center"> <div className="night-card mx-auto flex max-w-6xl flex-col items-start justify-between gap-7 rounded-[2rem] px-7 py-10 text-white sm:px-10 md:flex-row md:items-center">
<div> <div>
<h2 className="font-serif text-3xl font-semibold"> <h2 className="font-serif text-3xl font-semibold">
Privacy should feel understandable. Privacy should feel understandable.
+44 -33
View File
@@ -12,6 +12,7 @@ import {
import Link from "next/link"; import Link from "next/link";
import { ProductDevice } from "~/components/product-device"; import { ProductDevice } from "~/components/product-device";
import { QuestionsPreview } from "~/components/questions-preview";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { productClaims, productRelease } from "~/content/product"; import { productClaims, productRelease } from "~/content/product";
import { ACCESS_INVITE_MAILTO } from "~/lib/access"; import { ACCESS_INVITE_MAILTO } from "~/lib/access";
@@ -99,9 +100,7 @@ const accessFit = [
export default function HomePage() { export default function HomePage() {
return ( return (
<> <>
<section className="relative isolate min-h-[calc(100svh-4rem)] overflow-hidden px-4 pt-14 sm:px-6 sm:pt-20 lg:pt-24"> <section className="section-wash relative isolate min-h-[calc(100svh-4rem)] overflow-hidden px-4 pt-14 sm:px-6 sm:pt-20 lg:pt-24">
<div className="bg-primary-light/60 absolute -top-32 left-1/2 -z-10 h-[42rem] w-[74rem] max-w-[125vw] -translate-x-1/2 rounded-full blur-3xl" />
<div className="absolute top-48 -right-28 -z-10 size-[28rem] rounded-full bg-[#d9ecff]/35 blur-3xl dark:bg-[#123d5b]/28" />
<div className="mx-auto max-w-7xl text-center"> <div className="mx-auto max-w-7xl text-center">
<div className="animate-fade-in-up flex items-center justify-center gap-3"> <div className="animate-fade-in-up flex items-center justify-center gap-3">
<span className="bg-success-light text-success rounded-full px-3 py-1.5 text-xs font-bold tracking-[0.12em] uppercase"> <span className="bg-success-light text-success rounded-full px-3 py-1.5 text-xs font-bold tracking-[0.12em] uppercase">
@@ -158,7 +157,7 @@ export default function HomePage() {
</p> </p>
</section> </section>
<section className="pitch-lazy brand-warm border-border-soft/70 flex min-h-[78svh] items-center border-y px-4 py-24 sm:px-6 lg:py-32"> <section className="pitch-lazy section-wash flex min-h-[78svh] items-center px-4 py-24 sm:px-6 lg:py-32">
<div className="pitch-copy-motion mx-auto max-w-6xl text-center"> <div className="pitch-copy-motion mx-auto max-w-6xl text-center">
<p className="text-primary text-xs font-bold tracking-[0.2em] uppercase"> <p className="text-primary text-xs font-bold tracking-[0.2em] uppercase">
The connected picture The connected picture
@@ -184,11 +183,11 @@ export default function HomePage() {
Turn what happened into something you can check. Turn what happened into something you can check.
</h2> </h2>
<p className="text-muted-foreground mt-7 max-w-lg text-lg leading-relaxed"> <p className="text-muted-foreground mt-7 max-w-lg text-lg leading-relaxed">
Bring in typed notes, a portal screenshot, a document, or an Bring the important details from a visit into one reviewable
optional consent-gated recording. You decide what is useful and record. You decide what is useful and what belongs in the picture
what belongs in your record. you carry forward.
</p> </p>
<div className="border-primary/25 bg-primary-light/35 mt-8 max-w-lg rounded-2xl border p-5"> <div className="surface-panel mt-8 max-w-lg rounded-2xl p-5">
<p className="text-foreground text-sm leading-relaxed"> <p className="text-foreground text-sm leading-relaxed">
<strong className="font-semibold">The safety loop:</strong>{" "} <strong className="font-semibold">The safety loop:</strong>{" "}
review generated information before acting. Medscribe does not review generated information before acting. Medscribe does not
@@ -201,7 +200,7 @@ export default function HomePage() {
{visitMoments.map((moment, index) => ( {visitMoments.map((moment, index) => (
<li <li
key={moment.number} key={moment.number}
className="pitch-card-motion glass-panel relative overflow-hidden rounded-[2.5rem] px-5 pt-8 sm:px-10 sm:pt-10" className="pitch-card-motion surface-panel relative overflow-hidden rounded-[2.5rem] px-5 pt-8 sm:px-10 sm:pt-10"
> >
<div className="relative z-10 max-w-xl"> <div className="relative z-10 max-w-xl">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
@@ -272,7 +271,7 @@ export default function HomePage() {
</div> </div>
</div> </div>
<div className="grid gap-px overflow-hidden rounded-[2rem] border border-white/12 bg-white/12"> <div className="overflow-hidden rounded-[2rem] bg-white/[0.055]">
{[ {[
{ {
title: "Keep schedules easy to understand", title: "Keep schedules easy to understand",
@@ -289,7 +288,7 @@ export default function HomePage() {
].map((item) => ( ].map((item) => (
<article <article
key={item.title} key={item.title}
className="bg-[#0c2727]/88 p-7 backdrop-blur-sm sm:p-9" className="border-white/10 border-t p-7 first:border-t-0 sm:p-9"
> >
<Check className="size-5 text-teal-200" aria-hidden="true" /> <Check className="size-5 text-teal-200" aria-hidden="true" />
<h3 className="mt-5 font-serif text-2xl font-semibold sm:text-3xl"> <h3 className="mt-5 font-serif text-2xl font-semibold sm:text-3xl">
@@ -305,33 +304,45 @@ export default function HomePage() {
</div> </div>
</section> </section>
<section className="pitch-lazy brand-warm px-4 py-24 sm:px-6 lg:py-36"> <section className="pitch-lazy section-wash overflow-hidden px-4 py-24 sm:px-6 lg:py-36">
<div className="mx-auto grid max-w-7xl items-center gap-14 lg:grid-cols-[1fr_0.9fr] lg:gap-24"> <div
<div className="pitch-copy-motion order-2 lg:order-1"> id="questions"
className="mx-auto grid max-w-7xl scroll-mt-4 items-center gap-14 lg:grid-cols-[0.82fr_1.18fr] lg:gap-20"
>
<div className="pitch-copy-motion">
<p className="text-primary text-xs font-bold tracking-[0.2em] uppercase"> <p className="text-primary text-xs font-bold tracking-[0.2em] uppercase">
Ask from the record Questions for the doctor
</p> </p>
<h2 className="text-foreground mt-6 max-w-2xl font-serif text-[clamp(3.25rem,6vw,6.5rem)] leading-[0.94] font-semibold tracking-[-0.045em]"> <h2 className="text-foreground mt-6 max-w-2xl font-serif text-[clamp(3.25rem,6vw,6.5rem)] leading-[0.94] font-semibold tracking-[-0.045em]">
Better questions begin with your context. Ask now. Save what belongs in the room.
</h2> </h2>
<p className="text-muted-foreground mt-8 max-w-xl text-lg leading-relaxed sm:text-xl"> <p className="text-muted-foreground mt-8 max-w-xl text-lg leading-relaxed sm:text-xl">
Ask about a visit or medication using the context you have already Ask about a visit or medication. When the answer needs a care
collected. Answers help you prepare for a clearer conversation. professional&apos;s judgment, Medscribe can suggest saving the
question and who may be helpful to ask.
</p> </p>
<p className="border-primary/35 text-muted-foreground mt-7 max-w-xl border-l-2 pl-5 text-sm leading-relaxed"> <div className="text-muted-foreground mt-7 flex max-w-xl items-start gap-3 text-sm leading-relaxed">
Informational only. Medication changes still belong with a <span className="bg-primary-light text-primary mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full">
clinician or pharmacist. <Check className="size-3.5" aria-hidden="true" />
</span>
<p>
Keep a calm, ready list for your next appointment instead of
relying on memory when the conversation begins.
</p> </p>
</div> </div>
<div className="order-1 mx-auto w-[min(78vw,27rem)] lg:order-2"> </div>
<div className="mx-auto grid w-full max-w-[43rem] items-center justify-center gap-2 sm:grid-cols-[minmax(0,18rem)_minmax(0,22rem)] lg:justify-end">
<div className="mx-auto w-[min(72vw,18rem)] -rotate-2 sm:w-full">
<ProductDevice <ProductDevice
src="/screenshots/product-bezel-2026-08-23-complete/ask-black.webp" src="/screenshots/product-bezel-2026-08-23-complete/ask-black.webp"
darkSrc="/screenshots/product-bezel-2026-08-23-complete/ask-black-dark.webp" darkSrc="/screenshots/product-bezel-2026-08-23-complete/ask-black-dark.webp"
alt="Medscribe Ask response shown in a black titanium iPhone product bezel" alt="Medscribe Ask response shown in a black titanium iPhone product bezel"
className="pitch-device-motion pitch-device-drift-right" className="pitch-device-motion pitch-device-drift-left"
sizes="(min-width: 1024px) 432px, (min-width: 640px) 384px, 78vw" sizes="(min-width: 640px) 288px, 72vw"
/> />
</div> </div>
<QuestionsPreview className="pitch-card-motion w-full max-w-[22rem]" />
</div>
</div> </div>
</section> </section>
@@ -361,7 +372,7 @@ export default function HomePage() {
to review together. to review together.
</p> </p>
<div className="mt-9 grid gap-4 sm:grid-cols-2"> <div className="mt-9 grid gap-4 sm:grid-cols-2">
<div className="glass-panel rounded-2xl p-5"> <div className="surface-panel rounded-2xl p-5">
<ScanLine <ScanLine
className="text-primary size-6" className="text-primary size-6"
aria-hidden="true" aria-hidden="true"
@@ -373,7 +384,7 @@ export default function HomePage() {
Show or share a selected, readable snapshot. Show or share a selected, readable snapshot.
</p> </p>
</div> </div>
<div className="glass-panel rounded-2xl p-5"> <div className="surface-panel rounded-2xl p-5">
<ContactRound <ContactRound
className="text-primary size-6" className="text-primary size-6"
aria-hidden="true" aria-hidden="true"
@@ -389,7 +400,7 @@ export default function HomePage() {
</div> </div>
</div> </div>
<div className="brand-night mt-24 overflow-hidden rounded-[2.5rem] p-7 text-white shadow-[0_35px_90px_rgba(7,20,21,0.22)] sm:p-10 lg:p-14"> <div className="night-card mt-24 overflow-hidden rounded-[2.5rem] p-7 text-white sm:p-10 lg:p-14">
<div className="grid gap-12 lg:grid-cols-[0.85fr_1.15fr] lg:items-center"> <div className="grid gap-12 lg:grid-cols-[0.85fr_1.15fr] lg:items-center">
<div> <div>
<HeartHandshake <HeartHandshake
@@ -412,7 +423,7 @@ export default function HomePage() {
].map(([number, title, body]) => ( ].map(([number, title, body]) => (
<li <li
key={number} key={number}
className="rounded-2xl border border-white/15 bg-white/8 p-5" className="rounded-2xl bg-white/[0.065] p-5"
> >
<span className="flex size-8 items-center justify-center rounded-full bg-white/12 text-xs font-bold"> <span className="flex size-8 items-center justify-center rounded-full bg-white/12 text-xs font-bold">
{number} {number}
@@ -447,11 +458,11 @@ export default function HomePage() {
</p> </p>
</div> </div>
<dl className="mt-20 grid border-y border-white/12 md:grid-cols-3 md:divide-x md:divide-white/12"> <dl className="mt-20 grid gap-10 md:grid-cols-3 md:gap-0 md:divide-x md:divide-white/12">
{proofStats.map((stat) => ( {proofStats.map((stat) => (
<div <div
key={stat.label} key={stat.label}
className="border-b border-white/12 px-5 py-10 text-center last:border-b-0 md:border-b-0 lg:py-14" className="px-5 py-2 text-center md:py-10 lg:py-14"
> >
<dt className="text-sm text-slate-400">{stat.label}</dt> <dt className="text-sm text-slate-400">{stat.label}</dt>
<dd className="mt-3 font-serif text-5xl font-semibold tracking-[-0.04em] text-teal-200 sm:text-6xl lg:text-7xl"> <dd className="mt-3 font-serif text-5xl font-semibold tracking-[-0.04em] text-teal-200 sm:text-6xl lg:text-7xl">
@@ -468,7 +479,7 @@ export default function HomePage() {
return ( return (
<article <article
key={principle.title} key={principle.title}
className="rounded-[1.75rem] border border-white/12 bg-white/[0.045] p-7" className="rounded-[1.75rem] bg-white/[0.05] p-7"
> >
<Icon className="size-6 text-teal-200" aria-hidden="true" /> <Icon className="size-6 text-teal-200" aria-hidden="true" />
<h3 className="mt-8 font-serif text-2xl font-semibold"> <h3 className="mt-8 font-serif text-2xl font-semibold">
@@ -494,7 +505,7 @@ export default function HomePage() {
</section> </section>
<section className="pitch-lazy flex min-h-[82svh] items-center px-4 py-24 sm:px-6 lg:py-32"> <section className="pitch-lazy flex min-h-[82svh] items-center px-4 py-24 sm:px-6 lg:py-32">
<div className="pitch-copy-motion brand-warm border-border-soft/65 mx-auto w-full max-w-6xl overflow-hidden rounded-[3rem] border px-6 py-16 text-center shadow-[0_35px_110px_rgba(15,118,110,0.14)] sm:px-10 lg:px-20 lg:py-24"> <div className="pitch-copy-motion section-wash surface-panel mx-auto w-full max-w-6xl overflow-hidden rounded-[3rem] px-6 py-16 text-center sm:px-10 lg:px-20 lg:py-24">
<p className="text-primary text-xs font-bold tracking-[0.2em] uppercase"> <p className="text-primary text-xs font-bold tracking-[0.2em] uppercase">
Made for people who carry a lot Made for people who carry a lot
</p> </p>
@@ -509,7 +520,7 @@ export default function HomePage() {
{accessFit.map((item) => ( {accessFit.map((item) => (
<li <li
key={item} key={item}
className="border-primary/15 bg-surface/65 text-foreground flex items-start gap-3 rounded-2xl border p-4 text-sm leading-relaxed" className="bg-surface/65 text-foreground flex items-start gap-3 rounded-2xl p-4 text-sm leading-relaxed"
> >
<span className="bg-primary-light mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full"> <span className="bg-primary-light mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full">
<Check className="text-primary size-3" aria-hidden="true" /> <Check className="text-primary size-3" aria-hidden="true" />
+8 -5
View File
@@ -78,13 +78,16 @@ export default function PrivacyPage() {
</div> </div>
</section> </section>
<section className="brand-warm border-border-soft/70 border-y px-4 py-20 sm:px-6 lg:py-24"> <section className="section-wash px-4 py-20 sm:px-6 lg:py-24">
<div className="mx-auto grid max-w-6xl gap-4 md:grid-cols-3"> <div className="mx-auto grid max-w-6xl gap-4 md:grid-cols-3">
{principles.map((item) => { {principles.map((item) => {
const Icon = item.icon; const Icon = item.icon;
return ( return (
<article key={item.title} className="glass-panel rounded-2xl p-6"> <article
key={item.title}
className="surface-panel rounded-2xl p-6"
>
<Icon className="text-primary size-6" aria-hidden="true" /> <Icon className="text-primary size-6" aria-hidden="true" />
<h2 className="text-foreground mt-5 font-serif text-2xl font-semibold"> <h2 className="text-foreground mt-5 font-serif text-2xl font-semibold">
{item.title} {item.title}
@@ -116,7 +119,7 @@ export default function PrivacyPage() {
{commitments.map((item) => ( {commitments.map((item) => (
<article <article
key={item.title} key={item.title}
className="glass-panel rounded-2xl p-6 sm:p-7" className="surface-panel rounded-2xl p-6 sm:p-7"
> >
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
<div className="bg-primary-light text-primary flex size-10 shrink-0 items-center justify-center rounded-xl"> <div className="bg-primary-light text-primary flex size-10 shrink-0 items-center justify-center rounded-xl">
@@ -151,7 +154,7 @@ export default function PrivacyPage() {
{choices.map((item) => ( {choices.map((item) => (
<li <li
key={item} key={item}
className="flex items-start gap-3 rounded-xl border border-white/15 bg-white/8 p-4 text-sm leading-relaxed text-teal-50/90" className="flex items-start gap-3 rounded-xl bg-white/[0.06] p-4 text-sm leading-relaxed text-teal-50/90"
> >
<Check <Check
className="mt-0.5 size-4 shrink-0 text-teal-200" className="mt-0.5 size-4 shrink-0 text-teal-200"
@@ -165,7 +168,7 @@ export default function PrivacyPage() {
</section> </section>
<section className="px-4 py-18 sm:px-6"> <section className="px-4 py-18 sm:px-6">
<div className="glass-panel mx-auto flex max-w-6xl flex-col items-start justify-between gap-6 rounded-[2rem] p-7 sm:flex-row sm:items-center sm:p-9"> <div className="surface-panel mx-auto flex max-w-6xl flex-col items-start justify-between gap-6 rounded-[2rem] p-7 sm:flex-row sm:items-center sm:p-9">
<div> <div>
<h2 className="text-foreground font-serif text-3xl font-semibold"> <h2 className="text-foreground font-serif text-3xl font-semibold">
See how Medscribe fits into the day. See how Medscribe fits into the day.
+8 -1
View File
@@ -1,6 +1,6 @@
import "~/styles/globals.css"; import "~/styles/globals.css";
import { type Metadata } from "next"; import { type Metadata, type Viewport } from "next";
import { Instrument_Sans, Playfair_Display } from "next/font/google"; import { Instrument_Sans, Playfair_Display } from "next/font/google";
import { UmamiAnalytics } from "~/components/umami-analytics"; import { UmamiAnalytics } from "~/components/umami-analytics";
@@ -42,6 +42,13 @@ export const metadata: Metadata = {
}, },
}; };
export const viewport: Viewport = {
themeColor: [
{ media: "(prefers-color-scheme: light)", color: "#f4f9f7" },
{ media: "(prefers-color-scheme: dark)", color: "#071415" },
],
};
export default function RootLayout({ export default function RootLayout({
children, children,
}: Readonly<{ children: React.ReactNode }>) { }: Readonly<{ children: React.ReactNode }>) {
+10 -10
View File
@@ -20,7 +20,7 @@ const features = [
icon: CalendarDays, icon: CalendarDays,
title: "Turn a visit into context you can review", title: "Turn a visit into context you can review",
description: description:
"Type or paste notes, import a portal screenshot or document, or use consent-gated recording to create a summary you can check.", "Keep the important details from a visit together in a summary you can check and revisit.",
points: [ points: [
"Keep the visit and summary easy to revisit", "Keep the visit and summary easy to revisit",
"Review important details before relying on them", "Review important details before relying on them",
@@ -53,13 +53,13 @@ const features = [
id: "questions", id: "questions",
label: "Questions", label: "Questions",
icon: MessageCircle, icon: MessageCircle,
title: "Facts first, fluent answers second", title: "Keep the question for the right conversation",
description: description:
"Ask about a visit or medication using the context you have already collected. Dosing changes, diagnosis, and treatment decisions stay with a clinician or pharmacist.", "Ask about a visit or medication. When professional judgment is needed, save the question and keep guidance on who may be helpful to ask.",
points: [ points: [
"Keep answers connected to your own context", "Capture questions as they come up",
"Prepare for a clearer care conversation", "Keep a ready list for the next appointment",
"High-risk or unsupported questions defer to a professional", "See whether a doctor, prescriber, or pharmacist may be helpful",
], ],
image: "/screenshots/product-bezel-2026-08-23-complete/ask-black.webp", image: "/screenshots/product-bezel-2026-08-23-complete/ask-black.webp",
darkImage: darkImage:
@@ -143,10 +143,10 @@ export function FeatureShowcase() {
onClick={() => setActiveId(feature.id)} onClick={() => setActiveId(feature.id)}
onKeyDown={onTabKeyDown} onKeyDown={onTabKeyDown}
className={cn( className={cn(
"focus-visible:ring-primary/40 inline-flex min-h-11 shrink-0 items-center gap-2 rounded-full border px-4 py-2 text-sm font-semibold transition-colors outline-none focus-visible:ring-2", "focus-visible:ring-primary/40 inline-flex min-h-11 shrink-0 items-center gap-2 rounded-full px-4 py-2 text-sm font-semibold transition-colors outline-none focus-visible:ring-2",
isActive isActive
? "border-primary-button bg-primary-button text-white shadow-sm" ? "bg-primary-button text-white shadow-sm"
: "border-border-soft bg-surface/75 text-muted-foreground hover:border-primary/30 hover:text-foreground backdrop-blur-xl", : "bg-surface/75 text-muted-foreground hover:bg-surface-soft hover:text-foreground backdrop-blur-xl",
)} )}
> >
<Icon className="size-4" aria-hidden="true" /> <Icon className="size-4" aria-hidden="true" />
@@ -190,7 +190,7 @@ export function FeatureShowcase() {
</div> </div>
<div className="justify-self-center lg:justify-self-end"> <div className="justify-self-center lg:justify-self-end">
<div className="glass-panel rounded-[2.5rem] p-5 sm:p-7"> <div className="surface-panel rounded-[2.5rem] p-5 sm:p-7">
<PhoneMockup <PhoneMockup
src={active.image} src={active.image}
darkSrc={active.darkImage} darkSrc={active.darkImage}
+2 -3
View File
@@ -55,7 +55,7 @@ export function LoginForm({ redirectTo }: LoginFormProps) {
<div className="relative flex min-h-screen items-center justify-center px-4 py-16"> <div className="relative flex min-h-screen items-center justify-center px-4 py-16">
<ScreenBackground /> <ScreenBackground />
<Card className="w-full max-w-md border-border-soft/80 bg-surface/95 shadow-[0_20px_50px_rgba(15,23,42,0.12)] backdrop-blur-sm"> <Card className="w-full max-w-md bg-surface/95 shadow-[0_20px_50px_rgba(15,23,42,0.12)] backdrop-blur-sm">
<CardHeader className="items-center space-y-4 text-center"> <CardHeader className="items-center space-y-4 text-center">
<MedscribeLogo className="h-8 w-[100px] text-foreground" /> <MedscribeLogo className="h-8 w-[100px] text-foreground" />
<div className="space-y-2"> <div className="space-y-2">
@@ -81,7 +81,6 @@ export function LoginForm({ redirectTo }: LoginFormProps) {
name="password" name="password"
type="password" type="password"
autoComplete="current-password" autoComplete="current-password"
autoFocus
required required
value={password} value={password}
onChange={(event) => setPassword(event.target.value)} onChange={(event) => setPassword(event.target.value)}
@@ -103,7 +102,7 @@ export function LoginForm({ redirectTo }: LoginFormProps) {
className="w-full" className="w-full"
disabled={isSubmitting || password.length === 0} disabled={isSubmitting || password.length === 0}
> >
{isSubmitting ? "Checking..." : "Continue"} {isSubmitting ? "Checking" : "Continue"}
</Button> </Button>
</form> </form>
</CardContent> </CardContent>
+76
View File
@@ -0,0 +1,76 @@
import { ArrowUpRight, Check, MessageCircleQuestion, Plus } from "lucide-react";
import { cn } from "~/lib/utils";
const questions = [
{
question: "Could this change be related to my medication?",
recipient: "Prescriber",
},
{
question: "What should I expect at the follow-up?",
recipient: "Care team",
},
{
question: "Is this still the right time to take it?",
recipient: "Pharmacist",
},
] as const;
export function QuestionsPreview({ className }: { className?: string }) {
return (
<div
className={cn(
"surface-panel overflow-hidden rounded-[2rem] p-5 sm:p-6",
className,
)}
aria-label="Illustrative Questions list in Medscribe"
>
<div className="flex items-start justify-between gap-5 px-1 pb-5">
<div>
<p className="text-primary text-xs font-bold tracking-[0.16em] uppercase">
Questions
</p>
<h3 className="text-foreground mt-2 font-serif text-2xl font-semibold tracking-[-0.02em]">
Ready for the conversation
</h3>
</div>
<span className="bg-primary-light text-primary flex size-10 shrink-0 items-center justify-center rounded-full">
<MessageCircleQuestion className="size-5" aria-hidden="true" />
</span>
</div>
<div className="bg-canvas/70 overflow-hidden rounded-[1.35rem]">
{questions.map((item, index) => (
<div
key={item.question}
className={cn(
"grid grid-cols-[1fr_auto] gap-4 px-4 py-4 sm:px-5",
index > 0 && "border-border-soft/55 border-t",
)}
>
<div className="min-w-0">
<p className="text-foreground text-sm leading-snug font-semibold text-pretty">
{item.question}
</p>
<p className="text-muted-foreground mt-2 flex items-center gap-1.5 text-xs">
Ask your {item.recipient.toLowerCase()}
<ArrowUpRight className="size-3" aria-hidden="true" />
</p>
</div>
<span className="bg-success-light text-success mt-0.5 flex size-6 items-center justify-center rounded-full">
<Check className="size-3.5" aria-hidden="true" />
</span>
</div>
))}
</div>
<div className="text-muted-foreground mt-5 flex items-center gap-2 px-1 text-xs leading-relaxed">
<span className="bg-primary-light text-primary flex size-7 shrink-0 items-center justify-center rounded-full">
<Plus className="size-3.5" aria-hidden="true" />
</span>
Save a question the moment it comes to you.
</div>
</div>
);
}
-3
View File
@@ -2,9 +2,6 @@ export function ScreenBackground() {
return ( return (
<div className="pointer-events-none fixed inset-0 -z-10 overflow-hidden print:hidden"> <div className="pointer-events-none fixed inset-0 -z-10 overflow-hidden print:hidden">
<div className="ambient-canvas absolute inset-0" /> <div className="ambient-canvas absolute inset-0" />
<div className="bg-primary-light/22 absolute -top-48 -left-40 size-[34rem] rounded-full blur-3xl" />
<div className="absolute top-[28vh] -right-52 size-[38rem] rounded-full bg-[#d9ecff]/30 blur-3xl dark:bg-[#123d5b]/28" />
<div className="absolute bottom-[6vh] -left-56 size-[38rem] rounded-full bg-[#fbe9d7]/32 blur-3xl dark:bg-[#4a3026]/24" />
</div> </div>
); );
} }
+1 -1
View File
@@ -11,7 +11,7 @@ const footerLinks = [
export function SiteFooter() { export function SiteFooter() {
return ( return (
<footer className="print-hidden px-3 pb-3 sm:px-5 sm:pb-5"> <footer className="print-hidden px-3 pb-3 sm:px-5 sm:pb-5">
<div className="glass-panel mx-auto grid max-w-6xl gap-10 rounded-[2rem] px-6 py-10 text-sm sm:px-8 md:grid-cols-[1.3fr_0.7fr_0.7fr]"> <div className="surface-panel mx-auto grid max-w-6xl gap-10 rounded-[2rem] px-6 py-10 text-sm sm:px-8 md:grid-cols-[1.3fr_0.7fr_0.7fr]">
<div> <div>
<Link <Link
href="/" href="/"
+36 -10
View File
@@ -1,5 +1,6 @@
"use client"; "use client";
import { useEffect, useRef, useState } from "react";
import { Menu } from "lucide-react"; import { Menu } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
@@ -11,15 +12,38 @@ import { cn } from "~/lib/utils";
const navigation = [ const navigation = [
{ href: "/", label: "Home" }, { href: "/", label: "Home" },
{ href: "/how-it-works", label: "How it works" }, { href: "/how-it-works", label: "How it works" },
{ href: "/#questions", label: "Questions" },
{ href: "/privacy", label: "Privacy" }, { href: "/privacy", label: "Privacy" },
] as const; ] as const;
export function SiteHeader() { export function SiteHeader() {
const pathname = usePathname(); const pathname = usePathname();
const [hash, setHash] = useState("");
const mobileMenu = useRef<HTMLDetailsElement>(null);
useEffect(() => {
const updateHash = () => setHash(window.location.hash);
updateHash();
window.addEventListener("hashchange", updateHash);
return () => window.removeEventListener("hashchange", updateHash);
}, [pathname]);
const isActive = (href: (typeof navigation)[number]["href"]) => {
if (href === "/#questions") {
return pathname === "/" && hash === "#questions";
}
if (href === "/") {
return pathname === "/" && hash !== "#questions";
}
return pathname === href;
};
return ( return (
<header className="print-hidden sticky top-0 z-50 px-3 pt-3 sm:px-5"> <header className="print-hidden sticky top-0 z-50 px-3 pt-3 sm:px-5">
<div className="glass-panel-strong relative mx-auto flex h-14 max-w-6xl items-center gap-5 rounded-[1.25rem] px-3.5 sm:px-5"> <div className="surface-panel-strong relative mx-auto flex h-14 max-w-6xl items-center gap-5 rounded-[1.25rem] px-3.5 sm:px-5">
<Link <Link
href="/" href="/"
className="text-foreground focus-visible:ring-primary/40 absolute left-1/2 flex shrink-0 -translate-x-1/2 items-center gap-2.5 rounded-md transition-opacity outline-none hover:opacity-75 focus-visible:ring-2 md:static md:translate-x-0" className="text-foreground focus-visible:ring-primary/40 absolute left-1/2 flex shrink-0 -translate-x-1/2 items-center gap-2.5 rounded-md transition-opacity outline-none hover:opacity-75 focus-visible:ring-2 md:static md:translate-x-0"
@@ -39,11 +63,11 @@ export function SiteHeader() {
<Link <Link
key={item.href} key={item.href}
href={item.href} href={item.href}
aria-current={pathname === item.href ? "page" : undefined} aria-current={isActive(item.href) ? "page" : undefined}
className={cn( className={cn(
"focus-visible:ring-primary/40 rounded-full px-3 py-2 text-sm font-medium transition-all outline-none focus-visible:ring-2", "focus-visible:ring-primary/40 rounded-full px-3 py-2 text-sm font-medium transition-colors outline-none focus-visible:ring-2",
pathname === item.href isActive(item.href)
? "text-foreground border border-white/45 bg-white/[0.48] shadow-[inset_0_1px_0_rgba(255,255,255,0.5),0_5px_16px_rgba(15,118,110,0.1)] dark:border-white/10 dark:bg-white/8" ? "bg-primary-light/70 text-primary"
: "text-muted-foreground hover:bg-primary-light/60 hover:text-foreground", : "text-muted-foreground hover:bg-primary-light/60 hover:text-foreground",
)} )}
> >
@@ -55,23 +79,24 @@ export function SiteHeader() {
</Button> </Button>
</nav> </nav>
<details className="group relative ml-auto md:hidden"> <details ref={mobileMenu} className="group relative ml-auto md:hidden">
<summary className="border-border-soft/70 bg-surface/70 text-foreground focus-visible:ring-primary/40 flex size-10 cursor-pointer list-none items-center justify-center rounded-full border outline-none marker:content-none focus-visible:ring-2 [&::-webkit-details-marker]:hidden"> <summary className="bg-surface-soft text-foreground focus-visible:ring-primary/40 flex size-10 cursor-pointer list-none items-center justify-center rounded-full outline-none marker:content-none focus-visible:ring-2 [&::-webkit-details-marker]:hidden">
<Menu className="size-5" aria-hidden="true" /> <Menu className="size-5" aria-hidden="true" />
<span className="sr-only">Open navigation</span> <span className="sr-only">Open navigation</span>
</summary> </summary>
<nav <nav
aria-label="Mobile navigation" aria-label="Mobile navigation"
className="glass-panel-strong absolute top-12 right-0 flex w-64 flex-col rounded-[1.4rem] p-2" className="surface-panel-strong absolute top-12 right-0 flex w-64 flex-col rounded-[1.4rem] p-2"
> >
{navigation.map((item) => ( {navigation.map((item) => (
<Link <Link
key={item.href} key={item.href}
href={item.href} href={item.href}
aria-current={pathname === item.href ? "page" : undefined} onClick={() => mobileMenu.current?.removeAttribute("open")}
aria-current={isActive(item.href) ? "page" : undefined}
className={cn( className={cn(
"text-foreground focus-visible:ring-primary/40 rounded-xl px-4 py-3 text-sm font-medium outline-none focus-visible:ring-2", "text-foreground focus-visible:ring-primary/40 rounded-xl px-4 py-3 text-sm font-medium outline-none focus-visible:ring-2",
pathname === item.href isActive(item.href)
? "bg-primary-light/75 shadow-[inset_0_1px_0_rgba(255,255,255,0.35)]" ? "bg-primary-light/75 shadow-[inset_0_1px_0_rgba(255,255,255,0.35)]"
: "hover:bg-primary-light/60", : "hover:bg-primary-light/60",
)} )}
@@ -81,6 +106,7 @@ export function SiteHeader() {
))} ))}
<Link <Link
href="/access" href="/access"
onClick={() => mobileMenu.current?.removeAttribute("open")}
className="bg-primary-button hover:bg-primary-button/90 focus-visible:ring-primary/40 mt-1 rounded-xl px-4 py-3 text-center text-sm font-semibold text-white outline-none focus-visible:ring-2" className="bg-primary-button hover:bg-primary-button/90 focus-visible:ring-primary/40 mt-1 rounded-xl px-4 py-3 text-center text-sm font-semibold text-white outline-none focus-visible:ring-2"
> >
Request access Request access
+1 -1
View File
@@ -9,7 +9,7 @@ const badgeVariants = cva(
variants: { variants: {
variant: { variant: {
default: "bg-primary-light text-primary", default: "bg-primary-light text-primary",
outline: "border border-border-soft bg-surface text-muted-foreground", outline: "bg-surface-soft text-muted-foreground",
success: "bg-success-light text-success", success: "bg-success-light text-success",
}, },
}, },
+3 -3
View File
@@ -5,14 +5,14 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "~/lib/utils"; import { cn } from "~/lib/utils";
const buttonVariants = cva( const buttonVariants = cva(
"print-hidden inline-flex shrink-0 items-center justify-center gap-2 rounded-full text-sm font-semibold whitespace-nowrap transition-all outline-none focus-visible:ring-2 focus-visible:ring-primary/30 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", "print-hidden inline-flex shrink-0 items-center justify-center gap-2 rounded-full text-sm font-semibold whitespace-nowrap transition-[color,background-color,box-shadow,transform] duration-200 outline-none focus-visible:ring-2 focus-visible:ring-primary/30 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
{ {
variants: { variants: {
variant: { variant: {
default: default:
"bg-primary-button text-white shadow-[0_10px_25px_rgba(15,118,110,0.2)] hover:-translate-y-0.5 hover:bg-primary-button/90", "bg-primary-button text-white shadow-[0_8px_22px_rgba(15,118,110,0.16)] hover:-translate-y-0.5 hover:bg-primary-button/90",
outline: outline:
"border border-border-soft bg-surface/75 text-foreground backdrop-blur-xl hover:-translate-y-0.5 hover:bg-surface-soft", "bg-surface/85 text-foreground shadow-[0_4px_16px_rgba(15,23,42,0.06)] backdrop-blur-xl hover:-translate-y-0.5 hover:bg-surface-soft",
secondary: "bg-primary-light text-primary hover:bg-primary-light/80", secondary: "bg-primary-light text-primary hover:bg-primary-light/80",
ghost: ghost:
"text-muted-foreground hover:bg-surface-soft hover:text-foreground", "text-muted-foreground hover:bg-surface-soft hover:text-foreground",
+1 -1
View File
@@ -6,7 +6,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
className={cn( className={cn(
"rounded-lg border border-border-soft bg-surface text-foreground shadow-[0_1px_4px_rgba(0,0,0,0.05)]", "rounded-2xl bg-surface text-foreground shadow-[0_14px_40px_rgba(15,23,42,0.08)]",
className, className,
)} )}
{...props} {...props}
+121 -106
View File
@@ -22,13 +22,14 @@
@layer base { @layer base {
:root { :root {
color-scheme: light; color-scheme: light;
--ambient-teal: rgba(83, 211, 192, 0.24); --ambient-teal: rgba(83, 211, 192, 0.16);
--ambient-blue: rgba(132, 194, 255, 0.2); --ambient-blue: rgba(132, 194, 255, 0.12);
--ambient-warm: rgba(244, 184, 128, 0.2); --ambient-warm: rgba(244, 184, 128, 0.12);
--glass-fill: rgba(252, 255, 254, 0.74); --surface-fill: rgba(252, 255, 254, 0.88);
--glass-fill-strong: rgba(252, 255, 254, 0.9); --surface-fill-strong: rgba(252, 255, 254, 0.94);
--glass-border: rgba(255, 255, 255, 0.78); --surface-shadow: rgba(16, 65, 60, 0.09);
--glass-shadow: rgba(16, 65, 60, 0.13); --surface-highlight: rgba(255, 255, 255, 0.7);
--night: #0a1e1f;
} }
/* /*
@@ -51,18 +52,20 @@
--color-border-soft: #294243; --color-border-soft: #294243;
--color-success: #34d399; --color-success: #34d399;
--color-success-light: #124238; --color-success-light: #124238;
--ambient-teal: rgba(45, 212, 191, 0.18); --ambient-teal: rgba(45, 212, 191, 0.11);
--ambient-blue: rgba(41, 122, 179, 0.2); --ambient-blue: rgba(41, 122, 179, 0.13);
--ambient-warm: rgba(161, 91, 55, 0.16); --ambient-warm: rgba(161, 91, 55, 0.1);
--glass-fill: rgba(20, 39, 40, 0.68); --surface-fill: rgba(20, 39, 40, 0.84);
--glass-fill-strong: rgba(20, 39, 40, 0.9); --surface-fill-strong: rgba(20, 39, 40, 0.94);
--glass-border: rgba(180, 228, 218, 0.13); --surface-shadow: rgba(0, 0, 0, 0.22);
--glass-shadow: rgba(0, 0, 0, 0.34); --surface-highlight: rgba(180, 228, 218, 0.09);
--night: #061718;
} }
} }
html { html {
scroll-behavior: smooth; scroll-behavior: smooth;
scroll-padding-top: 6rem;
} }
body { body {
@@ -73,92 +76,118 @@
::selection { ::selection {
background: color-mix(in srgb, var(--color-primary) 24%, transparent); background: color-mix(in srgb, var(--color-primary) 24%, transparent);
} }
h1,
h2,
h3 {
text-wrap: balance;
}
p {
text-wrap: pretty;
}
a,
button,
summary {
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}
} }
.ambient-canvas { .ambient-canvas {
background: background:
radial-gradient( radial-gradient(
ellipse 78% 54% at 4% -8%, ellipse 72% 48% at 4% -8%,
var(--ambient-teal), var(--ambient-teal),
transparent 68%
),
radial-gradient(
ellipse 66% 50% at 102% 22%,
var(--ambient-blue),
transparent 70%
),
radial-gradient(
ellipse 64% 48% at -8% 88%,
var(--ambient-warm),
transparent 72% transparent 72%
), ),
radial-gradient(
ellipse 62% 46% at 102% 24%,
var(--ambient-blue),
transparent 74%
),
radial-gradient(
ellipse 60% 44% at -8% 88%,
var(--ambient-warm),
transparent 76%
),
var(--color-canvas); var(--color-canvas);
} }
.glass-panel { .surface-panel {
border: 1px solid var(--glass-border); background: var(--surface-fill);
background: var(--glass-fill);
box-shadow: box-shadow:
0 24px 70px var(--glass-shadow), 0 20px 58px var(--surface-shadow),
inset 0 1px 0 rgba(255, 255, 255, 0.32); inset 0 1px 0 var(--surface-highlight);
backdrop-filter: blur(24px) saturate(140%); backdrop-filter: blur(18px) saturate(115%);
-webkit-backdrop-filter: blur(24px) saturate(140%); -webkit-backdrop-filter: blur(18px) saturate(115%);
} }
.glass-panel-strong { .surface-panel-strong {
border: 1px solid var(--glass-border); background: var(--surface-fill-strong);
background: var(--glass-fill-strong);
box-shadow: box-shadow:
0 18px 55px var(--glass-shadow), 0 12px 42px var(--surface-shadow),
inset 0 1px 0 rgba(255, 255, 255, 0.28); inset 0 1px 0 var(--surface-highlight);
backdrop-filter: blur(30px) saturate(150%); backdrop-filter: blur(22px) saturate(120%);
-webkit-backdrop-filter: blur(30px) saturate(150%); -webkit-backdrop-filter: blur(22px) saturate(120%);
} }
.brand-warm { .section-wash {
background: background:
radial-gradient( radial-gradient(
circle at 84% 12%, ellipse 52% 36% at 88% 30%,
color-mix(in srgb, var(--ambient-blue) 82%, transparent), color-mix(in srgb, var(--ambient-blue) 72%, transparent),
transparent 34% transparent 72%
), ),
linear-gradient(145deg, #fbf1e7 0%, #f4f9f7 56%, #e8f7f3 100%); radial-gradient(
ellipse 48% 34% at 8% 68%,
color-mix(in srgb, var(--ambient-warm) 68%, transparent),
transparent 74%
);
} }
.brand-night { .brand-night {
background: background:
radial-gradient( radial-gradient(
circle at 82% 12%, ellipse 58% 30% at 82% 32%,
rgba(45, 212, 191, 0.21), rgba(45, 212, 191, 0.1),
transparent 34% transparent 72%
), ),
radial-gradient( radial-gradient(
circle at 8% 88%, ellipse 46% 28% at 8% 72%,
rgba(58, 134, 185, 0.17), rgba(58, 134, 185, 0.08),
transparent 31% transparent 76%
), ),
linear-gradient(150deg, #071415 0%, #0b2928 54%, #102b3c 100%); linear-gradient(
to bottom,
color-mix(in srgb, var(--color-canvas) 100%, transparent) 0,
var(--night) 5rem,
var(--night) calc(100% - 5rem),
color-mix(in srgb, var(--color-canvas) 100%, transparent) 100%
);
} }
@media (prefers-color-scheme: dark) { .night-card {
.brand-warm {
background: background:
radial-gradient( radial-gradient(
circle at 84% 12%, ellipse 58% 42% at 88% 12%,
rgba(41, 122, 179, 0.17), rgba(45, 212, 191, 0.12),
transparent 34% transparent 72%
), ),
radial-gradient( linear-gradient(145deg, #0a2021 0%, #0a1e1f 62%, #0d252d 100%);
circle at 8% 84%, box-shadow: 0 30px 82px rgba(4, 20, 21, 0.2);
rgba(161, 91, 55, 0.14),
transparent 36%
),
linear-gradient(145deg, #142421 0%, #0d1f20 58%, #102a32 100%);
}
} }
.card-hover { .card-hover {
@apply transition-all duration-200 hover:-translate-y-0.5 hover:shadow-[0_8px_24px_rgba(15,23,42,0.08)]; transition:
transform 200ms ease,
box-shadow 200ms ease;
}
.card-hover:hover {
transform: translateY(-2px);
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.08);
} }
@keyframes fade-in-up { @keyframes fade-in-up {
@@ -188,12 +217,6 @@
animation: fade-in-up 0.5s ease-out 0.3s both; animation: fade-in-up 0.5s ease-out 0.3s both;
} }
/* Long-form homepage chapters render as they approach the viewport. */
.pitch-lazy {
content-visibility: auto;
contain-intrinsic-size: auto 1000px;
}
/* /*
* Scroll-scrubbed product motion. This stays CSS-only so the long homepage * Scroll-scrubbed product motion. This stays CSS-only so the long homepage
* remains a server component and ships no scroll listener or animation * remains a server component and ships no scroll listener or animation
@@ -201,69 +224,61 @@
*/ */
.pitch-device-motion { .pitch-device-motion {
--pitch-enter-x: 0px; --pitch-enter-x: 0px;
--pitch-enter-y: 7rem; --pitch-enter-y: 3.5rem;
--pitch-enter-rotate: 0deg;
--pitch-exit-x: 0px; --pitch-exit-x: 0px;
--pitch-exit-y: -5rem; --pitch-exit-y: -2rem;
--pitch-exit-rotate: 0deg;
backface-visibility: hidden; backface-visibility: hidden;
transform-origin: center; transform-origin: center;
} }
.pitch-device-drift-left { .pitch-device-drift-left {
--pitch-enter-x: -2.25rem; --pitch-enter-x: -1rem;
--pitch-enter-rotate: -2deg; --pitch-exit-x: 0.5rem;
--pitch-exit-x: 1.5rem;
--pitch-exit-rotate: 1.25deg;
} }
.pitch-device-drift-right { .pitch-device-drift-right {
--pitch-enter-x: 2.25rem; --pitch-enter-x: 1rem;
--pitch-enter-rotate: 2deg; --pitch-exit-x: -0.5rem;
--pitch-exit-x: -1.5rem;
--pitch-exit-rotate: -1.25deg;
} }
@keyframes pitch-device-scroll { @keyframes pitch-device-scroll {
0% { 0% {
opacity: 0.42; opacity: 0.78;
transform: translate3d(var(--pitch-enter-x), var(--pitch-enter-y), 0) transform: translate3d(var(--pitch-enter-x), var(--pitch-enter-y), 0);
scale(0.92) rotate(var(--pitch-enter-rotate));
} }
38% { 34% {
opacity: 1; opacity: 1;
transform: translate3d(0, 0, 0) scale(1) rotate(0deg); transform: translate3d(0, 0, 0);
} }
72% { 78% {
opacity: 1; opacity: 1;
transform: translate3d(0, -1.5rem, 0) scale(1.018) rotate(0deg); transform: translate3d(0, -0.75rem, 0);
} }
100% { 100% {
opacity: 0.78; opacity: 0.92;
transform: translate3d(var(--pitch-exit-x), var(--pitch-exit-y), 0) transform: translate3d(var(--pitch-exit-x), var(--pitch-exit-y), 0);
scale(0.97) rotate(var(--pitch-exit-rotate));
} }
} }
@keyframes pitch-copy-scroll { @keyframes pitch-copy-scroll {
from { from {
opacity: 0.2; opacity: 0.56;
transform: translate3d(0, 3.5rem, 0) scale(0.985); transform: translate3d(0, 2rem, 0);
} }
to { to {
opacity: 1; opacity: 1;
transform: translate3d(0, 0, 0) scale(1); transform: translate3d(0, 0, 0);
} }
} }
@keyframes pitch-card-scroll { @keyframes pitch-card-scroll {
from { from {
opacity: 0.58; opacity: 0.76;
transform: translate3d(0, 4rem, 0) scale(0.97); transform: translate3d(0, 2rem, 0);
} }
to { to {
opacity: 1; opacity: 1;
transform: translate3d(0, 0, 0) scale(1); transform: translate3d(0, 0, 0);
} }
} }
@@ -277,31 +292,31 @@
.pitch-copy-motion { .pitch-copy-motion {
animation: pitch-copy-scroll linear both; animation: pitch-copy-scroll linear both;
animation-timeline: view(block); animation-timeline: view(block);
animation-range: entry 5% cover 38%; animation-range: entry 0% cover 28%;
} }
.pitch-card-motion { .pitch-card-motion {
animation: pitch-card-scroll linear both; animation: pitch-card-scroll linear both;
animation-timeline: view(block); animation-timeline: view(block);
animation-range: entry 0% cover 32%; animation-range: entry 0% cover 24%;
} }
} }
/* Keep the product choreography visible on phones without crowding the copy. */ /* Keep the product choreography visible on phones without crowding the copy. */
@media (max-width: 639px) { @media (max-width: 639px) {
.pitch-device-motion { .pitch-device-motion {
--pitch-enter-y: 4rem; --pitch-enter-y: 2.5rem;
--pitch-exit-y: -3rem; --pitch-exit-y: -1.25rem;
} }
.pitch-device-drift-left { .pitch-device-drift-left {
--pitch-enter-x: -1.25rem; --pitch-enter-x: -0.5rem;
--pitch-exit-x: 0.75rem; --pitch-exit-x: 0.25rem;
} }
.pitch-device-drift-right { .pitch-device-drift-right {
--pitch-enter-x: 1.25rem; --pitch-enter-x: 0.5rem;
--pitch-exit-x: -0.75rem; --pitch-exit-x: -0.25rem;
} }
} }