Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81109f36ea |
@@ -20,6 +20,7 @@ next-env.d.ts
|
|||||||
|
|
||||||
# production
|
# production
|
||||||
/build
|
/build
|
||||||
|
/output/
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
@@ -43,4 +44,4 @@ yarn-error.log*
|
|||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
||||||
# idea files
|
# idea files
|
||||||
.idea
|
.idea
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
# Medscribe Web
|
# Medscribe Web
|
||||||
|
|
||||||
Marketing homepage for [Medscribe](../README.md) — a privacy-first, on-device medical companion for iOS and Android.
|
Public product website for [Medscribe](../README.md) — a privacy-first,
|
||||||
|
on-device medical companion in internal beta on iOS and Android.
|
||||||
|
|
||||||
Built with [create-t3-app](https://create.t3.gg/) (Next.js + TypeScript + Tailwind). Single-page site; no database or API routes.
|
Built with [create-t3-app](https://create.t3.gg/) (Next.js + TypeScript +
|
||||||
|
Tailwind). There is no product database or beta-form backend.
|
||||||
|
|
||||||
Uses **bun** exclusively (`bun.lock`).
|
Uses **bun** exclusively (`bun.lock`).
|
||||||
|
|
||||||
@@ -15,6 +17,26 @@ bun run dev
|
|||||||
|
|
||||||
Open [http://localhost:3000](http://localhost:3000).
|
Open [http://localhost:3000](http://localhost:3000).
|
||||||
|
|
||||||
|
## Routes
|
||||||
|
|
||||||
|
The first public product slice includes:
|
||||||
|
|
||||||
|
- `/` — consumer-first portable-context story
|
||||||
|
- `/how-it-works` — inputs, review loop, medication maintenance, and handoff
|
||||||
|
- `/privacy` — data map, sharing limitations, and evidence boundaries
|
||||||
|
- `/beta` — expectations and a health-data-free email request
|
||||||
|
|
||||||
|
Named partner print material remains isolated and excluded from search:
|
||||||
|
|
||||||
|
- `/briefs/northwell`
|
||||||
|
- `/cvs`
|
||||||
|
- `/walgreens`
|
||||||
|
|
||||||
|
`/executive-summary` and `/landscape` remain retired/internal and are also
|
||||||
|
excluded from search. `SITE_PASSWORD` still gates the entire site when set; a
|
||||||
|
future pass should make protection route-specific before the public site is
|
||||||
|
deployed alongside private briefs.
|
||||||
|
|
||||||
### Analytics (optional)
|
### Analytics (optional)
|
||||||
|
|
||||||
Set in `.env` (see `.env.example`):
|
Set in `.env` (see `.env.example`):
|
||||||
@@ -31,7 +53,10 @@ bun run build
|
|||||||
bun run start
|
bun run start
|
||||||
```
|
```
|
||||||
|
|
||||||
Screenshots live in `public/screenshots/`. They are wrapped in an iPhone-style frame via [`react-device-mockup`](https://github.com/jung-youngmin/react-device-mockup).
|
Screenshots live in `public/screenshots/`. The long-form homepage uses
|
||||||
|
precomposited, transparent iPhone product-bezel assets in
|
||||||
|
`public/screenshots/pitch/`; supporting pages use the lightweight CSS frame.
|
||||||
|
Public pages label product imagery as synthetic demo data and beta UI.
|
||||||
|
|
||||||
### Capturing raw screenshots for the site
|
### Capturing raw screenshots for the site
|
||||||
|
|
||||||
@@ -39,6 +64,11 @@ Export **in-app content only** from the simulator or device — no simulator chr
|
|||||||
|
|
||||||
1. Run the app on **iPhone 17 Pro** (or 16 Pro) simulator or device, or on an **Android** emulator/device.
|
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).
|
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`.
|
3. Drop PNGs into `public/screenshots/` and update paths in the relevant route
|
||||||
|
or `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.
|
The CSS 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. Homepage product images are
|
||||||
|
composited separately at 810 × 1656 with transparent backgrounds and rendered
|
||||||
|
through `src/components/product-device.tsx`.
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
|
"puppeteer-core": "^25.4.0",
|
||||||
"tailwindcss": "^4.0.15",
|
"tailwindcss": "^4.0.15",
|
||||||
"typescript": "^5.8.2",
|
"typescript": "^5.8.2",
|
||||||
"typescript-eslint": "^8.27.0",
|
"typescript-eslint": "^8.27.0",
|
||||||
@@ -221,6 +222,8 @@
|
|||||||
|
|
||||||
"@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="],
|
"@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="],
|
||||||
|
|
||||||
|
"@puppeteer/browsers": ["@puppeteer/browsers@3.0.6", "", { "dependencies": { "modern-tar": "^0.7.6", "yargs": "^18.0.0" }, "peerDependencies": { "proxy-agent": ">=8.0.1", "yauzl": "^2.10.0 || ^3.4.0" }, "optionalPeers": ["proxy-agent", "yauzl"], "bin": { "browsers": "lib/main-cli.js" } }, "sha512-B/gKoqlFkzhvzsI6jo9K1cZz9o5ypviVv/xu8CwA4grZzyVwN+XfkT+tu8T1zrauuEXv6VhS2oGX+6NL95WcKA=="],
|
||||||
|
|
||||||
"@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA=="],
|
"@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA=="],
|
||||||
|
|
||||||
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.3.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA=="],
|
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.3.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA=="],
|
||||||
@@ -349,6 +352,8 @@
|
|||||||
|
|
||||||
"ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
|
"ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
|
||||||
|
|
||||||
|
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
||||||
|
|
||||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||||
|
|
||||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||||
@@ -403,10 +408,14 @@
|
|||||||
|
|
||||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||||
|
|
||||||
|
"chromium-bidi": ["chromium-bidi@17.0.2", "", { "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-5v9GQFhTktFvotn/OFNJBmKLKRAb6n9r0bVCwf7sHgWc3/JryK0bj1nn93L3pHFrfgcsu6Be6EWsDi+1XHTGDg=="],
|
||||||
|
|
||||||
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
|
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
|
||||||
|
|
||||||
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
|
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
|
||||||
|
|
||||||
|
"cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="],
|
||||||
|
|
||||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||||
|
|
||||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||||
@@ -437,6 +446,8 @@
|
|||||||
|
|
||||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||||
|
|
||||||
|
"devtools-protocol": ["devtools-protocol@0.0.1653615", "", {}, "sha512-pGVkY3T/qXxAp2nFPodwYqOevk6ncNMSmvL8QfRCx5ZWGd6Vor7AFNmyaA8Zs6uJyP1QAfjuLandCgvSix1BNA=="],
|
||||||
|
|
||||||
"doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
|
"doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
|
||||||
|
|
||||||
"drizzle-kit": ["drizzle-kit@0.31.10", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="],
|
"drizzle-kit": ["drizzle-kit@0.31.10", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="],
|
||||||
@@ -469,6 +480,8 @@
|
|||||||
|
|
||||||
"esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
|
"esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
|
||||||
|
|
||||||
|
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||||
|
|
||||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||||
|
|
||||||
"eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "bin": "bin/eslint.js" }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="],
|
"eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "bin": "bin/eslint.js" }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="],
|
||||||
@@ -537,6 +550,10 @@
|
|||||||
|
|
||||||
"generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="],
|
"generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="],
|
||||||
|
|
||||||
|
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||||
|
|
||||||
|
"get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="],
|
||||||
|
|
||||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||||
|
|
||||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||||
@@ -705,6 +722,10 @@
|
|||||||
|
|
||||||
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||||
|
|
||||||
|
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
|
||||||
|
|
||||||
|
"modern-tar": ["modern-tar@0.7.7", "", {}, "sha512-t9VmxaqrmANnEOBhpSDI6HD192Ge48k8vmWqQQL7hSFEqHEYwZbbsu49+aKLWZeRvFs3j1pMhXOqqF4kPlvjkQ=="],
|
||||||
|
|
||||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
|
|
||||||
"nanoid": ["nanoid@3.3.15", "", { "bin": "bin/nanoid.cjs" }, "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA=="],
|
"nanoid": ["nanoid@3.3.15", "", { "bin": "bin/nanoid.cjs" }, "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA=="],
|
||||||
@@ -767,6 +788,8 @@
|
|||||||
|
|
||||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||||
|
|
||||||
|
"puppeteer-core": ["puppeteer-core@25.4.0", "", { "dependencies": { "@puppeteer/browsers": "3.0.6", "chromium-bidi": "17.0.2", "devtools-protocol": "0.0.1653615", "typed-query-selector": "^2.12.2", "webdriver-bidi-protocol": "0.4.2", "ws": "^8.21.1" } }, "sha512-K1plkLOdeoUnGeT1OvdqF3qxl33v+Ra/uH5VyPEhXdMcpvGiEskHzxxEU3fgpccJpJLIipB/rPUsvkZRWeKqOA=="],
|
||||||
|
|
||||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||||
|
|
||||||
"react": ["react@19.2.7", "", {}, "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ=="],
|
"react": ["react@19.2.7", "", {}, "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ=="],
|
||||||
@@ -831,6 +854,8 @@
|
|||||||
|
|
||||||
"stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
|
"stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
|
||||||
|
|
||||||
|
"string-width": ["string-width@8.2.2", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg=="],
|
||||||
|
|
||||||
"string.prototype.includes": ["string.prototype.includes@2.0.1", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3" } }, "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg=="],
|
"string.prototype.includes": ["string.prototype.includes@2.0.1", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3" } }, "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg=="],
|
||||||
|
|
||||||
"string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="],
|
"string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="],
|
||||||
@@ -843,6 +868,8 @@
|
|||||||
|
|
||||||
"string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="],
|
"string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="],
|
||||||
|
|
||||||
|
"strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
|
||||||
|
|
||||||
"strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
|
"strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
|
||||||
|
|
||||||
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||||
@@ -881,6 +908,8 @@
|
|||||||
|
|
||||||
"typed-array-length": ["typed-array-length@1.0.8", "", { "dependencies": { "call-bind": "^1.0.9", "for-each": "^0.3.5", "gopd": "^1.2.0", "is-typed-array": "^1.1.15", "possible-typed-array-names": "^1.1.0", "reflect.getprototypeof": "^1.0.10" } }, "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g=="],
|
"typed-array-length": ["typed-array-length@1.0.8", "", { "dependencies": { "call-bind": "^1.0.9", "for-each": "^0.3.5", "gopd": "^1.2.0", "is-typed-array": "^1.1.15", "possible-typed-array-names": "^1.1.0", "reflect.getprototypeof": "^1.0.10" } }, "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g=="],
|
||||||
|
|
||||||
|
"typed-query-selector": ["typed-query-selector@2.12.2", "", {}, "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ=="],
|
||||||
|
|
||||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||||
|
|
||||||
"typescript-eslint": ["typescript-eslint@8.62.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.62.0", "@typescript-eslint/parser": "8.62.0", "@typescript-eslint/typescript-estree": "8.62.0", "@typescript-eslint/utils": "8.62.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8QxXi+ZACKX0kaqO4gY8kn0RSD9gFfaHDWwjqtEN48aWCBkX4MJaufWN+c3BzlrXLOxfywDL8CaoqUwcRq4j4Q=="],
|
"typescript-eslint": ["typescript-eslint@8.62.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.62.0", "@typescript-eslint/parser": "8.62.0", "@typescript-eslint/typescript-estree": "8.62.0", "@typescript-eslint/utils": "8.62.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8QxXi+ZACKX0kaqO4gY8kn0RSD9gFfaHDWwjqtEN48aWCBkX4MJaufWN+c3BzlrXLOxfywDL8CaoqUwcRq4j4Q=="],
|
||||||
@@ -893,6 +922,8 @@
|
|||||||
|
|
||||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||||
|
|
||||||
|
"webdriver-bidi-protocol": ["webdriver-bidi-protocol@0.4.2", "", {}, "sha512-VSV+fzfChirL3e7jay2yUC7B4HQCGtEWEg/MSSQbK+qWbqeGlRLlXTzPpYr3XGUvbpDHumWZBJxgesg4N7dbtA=="],
|
||||||
|
|
||||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||||
|
|
||||||
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
|
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
|
||||||
@@ -905,6 +936,16 @@
|
|||||||
|
|
||||||
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||||
|
|
||||||
|
"wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
|
||||||
|
|
||||||
|
"ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="],
|
||||||
|
|
||||||
|
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||||
|
|
||||||
|
"yargs": ["yargs@18.1.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^8.2.1", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg=="],
|
||||||
|
|
||||||
|
"yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
|
||||||
|
|
||||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||||
|
|
||||||
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||||
@@ -923,6 +964,8 @@
|
|||||||
|
|
||||||
"@unrs/resolver-binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
|
"@unrs/resolver-binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
|
||||||
|
|
||||||
|
"cliui/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||||
|
|
||||||
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||||
|
|
||||||
"eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
"eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||||
@@ -941,6 +984,10 @@
|
|||||||
|
|
||||||
"tsx/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
|
"tsx/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
|
||||||
|
|
||||||
|
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||||
|
|
||||||
|
"wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||||
|
|
||||||
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
|
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
|
||||||
|
|
||||||
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="],
|
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="],
|
||||||
@@ -987,6 +1034,8 @@
|
|||||||
|
|
||||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
|
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
|
||||||
|
|
||||||
|
"cliui/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
|
"tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="],
|
"tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="],
|
||||||
@@ -1039,6 +1088,8 @@
|
|||||||
|
|
||||||
"tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
|
"tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
|
||||||
|
|
||||||
|
"wrap-ansi/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
"@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
|
"puppeteer-core": "^25.4.0",
|
||||||
"tailwindcss": "^4.0.15",
|
"tailwindcss": "^4.0.15",
|
||||||
"typescript": "^5.8.2",
|
"typescript": "^5.8.2",
|
||||||
"typescript-eslint": "^8.27.0"
|
"typescript-eslint": "^8.27.0"
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="2550" height="3300" viewBox="0 0 850 1100" preserveAspectRatio="xMidYMin slice">
|
||||||
|
<defs>
|
||||||
|
<pattern id="grid" width="28" height="28" patternUnits="userSpaceOnUse">
|
||||||
|
<path d="M 28 0 L 0 0 0 28" fill="none" stroke="#0f172a" stroke-opacity="0.055" stroke-width="1"/>
|
||||||
|
</pattern>
|
||||||
|
<linearGradient id="wash" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0%" stop-color="#ccfbf1" stop-opacity="0.5"/>
|
||||||
|
<stop offset="100%" stop-color="#ccfbf1" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="850" height="1100" fill="#f8fafc"/>
|
||||||
|
<rect width="850" height="1100" fill="url(#grid)"/>
|
||||||
|
<rect width="850" height="260" fill="url(#wash)"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 722 B |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 63 KiB |
@@ -0,0 +1,108 @@
|
|||||||
|
// Capture the long-form homepage in both system color schemes.
|
||||||
|
//
|
||||||
|
// bun scripts/capture-homepage.mjs [baseUrl] [outputDirectory]
|
||||||
|
//
|
||||||
|
// Requires a running local server and Google Chrome. If SITE_PASSWORD is set,
|
||||||
|
// the script uses the same signed access cookie as the login route.
|
||||||
|
import crypto from "node:crypto";
|
||||||
|
import fs from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
import puppeteer from "puppeteer-core";
|
||||||
|
|
||||||
|
const baseUrl = process.argv[2] ?? "http://localhost:3000";
|
||||||
|
const outputDirectory = path.resolve(process.argv[3] ?? "output");
|
||||||
|
const chrome = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
|
||||||
|
const password = process.env.SITE_PASSWORD;
|
||||||
|
|
||||||
|
await fs.mkdir(outputDirectory, { recursive: true });
|
||||||
|
|
||||||
|
const browser = await puppeteer.launch({
|
||||||
|
executablePath: chrome,
|
||||||
|
headless: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
async function capture({ name, width, height, colorScheme }) {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await page.setViewport({ width, height, deviceScaleFactor: 1 });
|
||||||
|
await page.emulateMediaFeatures([
|
||||||
|
{ name: "prefers-color-scheme", value: colorScheme },
|
||||||
|
{ name: "prefers-reduced-motion", value: "reduce" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (password) {
|
||||||
|
const value = crypto
|
||||||
|
.createHmac("sha256", password)
|
||||||
|
.update("medscribe-site-access-v1")
|
||||||
|
.digest("base64");
|
||||||
|
await browser.setCookie({
|
||||||
|
name: "medscribe_site_access",
|
||||||
|
value,
|
||||||
|
url: baseUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.goto(baseUrl, {
|
||||||
|
waitUntil: "domcontentloaded",
|
||||||
|
timeout: 60_000,
|
||||||
|
});
|
||||||
|
if (page.url().includes("/login")) {
|
||||||
|
throw new Error("Homepage capture was redirected to /login.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.addStyleTag({
|
||||||
|
content:
|
||||||
|
".pitch-lazy{content-visibility:visible!important}*{animation:none!important;transition:none!important}",
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.evaluate(async () => {
|
||||||
|
await document.fonts.ready;
|
||||||
|
for (let y = 0; y < document.documentElement.scrollHeight; y += 700) {
|
||||||
|
window.scrollTo(0, y);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||||
|
}
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
});
|
||||||
|
await page.waitForFunction(
|
||||||
|
() => Array.from(document.images).every((image) => image.complete),
|
||||||
|
{ timeout: 30_000 },
|
||||||
|
);
|
||||||
|
|
||||||
|
const layout = await page.evaluate(() => ({
|
||||||
|
clientWidth: document.documentElement.clientWidth,
|
||||||
|
scrollWidth: document.documentElement.scrollWidth,
|
||||||
|
scrollHeight: document.documentElement.scrollHeight,
|
||||||
|
}));
|
||||||
|
if (layout.scrollWidth > layout.clientWidth) {
|
||||||
|
throw new Error(
|
||||||
|
`${name} has horizontal overflow: ${layout.scrollWidth}px > ${layout.clientWidth}px`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const destination = path.join(outputDirectory, `${name}.png`);
|
||||||
|
await page.screenshot({ path: destination, fullPage: true });
|
||||||
|
console.log(`${name}: ${layout.clientWidth} × ${layout.scrollHeight}`);
|
||||||
|
} finally {
|
||||||
|
await page.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const colorScheme of ["light", "dark"]) {
|
||||||
|
await capture({
|
||||||
|
name: `homepage-${colorScheme}`,
|
||||||
|
width: 1440,
|
||||||
|
height: 1000,
|
||||||
|
colorScheme,
|
||||||
|
});
|
||||||
|
await capture({
|
||||||
|
name: `homepage-${colorScheme}-mobile`,
|
||||||
|
width: 390,
|
||||||
|
height: 844,
|
||||||
|
colorScheme,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await browser.close();
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// Render a marketing route to a print-styled PDF, the same way the browser's
|
||||||
|
// "Print PDF" button does (print CSS + designed print-only components).
|
||||||
|
//
|
||||||
|
// bun scripts/render-pdf.mjs <route> <output.pdf> [baseUrl]
|
||||||
|
// bun scripts/render-pdf.mjs /cvs ../docs/business/CVS_SUMMARY.pdf
|
||||||
|
//
|
||||||
|
// Self-authenticates past the SITE_PASSWORD gate by computing the same signed
|
||||||
|
// access cookie the login route issues (see src/lib/site-auth.ts). Requires a
|
||||||
|
// running dev/preview server and a local Google Chrome.
|
||||||
|
import crypto from "node:crypto";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import puppeteer from "puppeteer-core";
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
const route = process.argv[2] ?? "/cvs";
|
||||||
|
const outArg = process.argv[3] ?? "../docs/business/CVS_SUMMARY.pdf";
|
||||||
|
const baseUrl = process.argv[4] ?? "http://localhost:3000";
|
||||||
|
const outPath = path.resolve(__dirname, "..", outArg);
|
||||||
|
|
||||||
|
const CHROME =
|
||||||
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
|
||||||
|
const AUTH_COOKIE_NAME = "medscribe_site_access";
|
||||||
|
const ACCESS_PAYLOAD = "medscribe-site-access-v1";
|
||||||
|
|
||||||
|
const password = process.env.SITE_PASSWORD;
|
||||||
|
const cookies = [];
|
||||||
|
if (password) {
|
||||||
|
const token = crypto
|
||||||
|
.createHmac("sha256", password)
|
||||||
|
.update(ACCESS_PAYLOAD)
|
||||||
|
.digest("base64");
|
||||||
|
cookies.push({
|
||||||
|
name: AUTH_COOKIE_NAME,
|
||||||
|
value: token,
|
||||||
|
url: baseUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const browser = await puppeteer.launch({
|
||||||
|
executablePath: CHROME,
|
||||||
|
headless: true,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
if (cookies.length) await browser.setCookie(...cookies);
|
||||||
|
const res = await page.goto(`${baseUrl}${route}`, {
|
||||||
|
waitUntil: "networkidle0",
|
||||||
|
timeout: 60_000,
|
||||||
|
});
|
||||||
|
const finalUrl = page.url();
|
||||||
|
if (finalUrl.includes("/login")) {
|
||||||
|
throw new Error(
|
||||||
|
`Redirected to login (${res?.status()}). Check SITE_PASSWORD is set in the env used to run this script.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await page.pdf({
|
||||||
|
path: outPath,
|
||||||
|
printBackground: true,
|
||||||
|
preferCSSPageSize: true,
|
||||||
|
});
|
||||||
|
console.log(`Wrote ${outPath}`);
|
||||||
|
} finally {
|
||||||
|
await browser.close();
|
||||||
|
}
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import {
|
||||||
|
ArrowRight,
|
||||||
|
Check,
|
||||||
|
ClipboardCheck,
|
||||||
|
Mail,
|
||||||
|
MessageSquareText,
|
||||||
|
RotateCcw,
|
||||||
|
Smartphone,
|
||||||
|
Users,
|
||||||
|
} from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import { SectionLabel } from "~/components/section-label";
|
||||||
|
import { Badge } from "~/components/ui/badge";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
import { productRelease } from "~/content/product";
|
||||||
|
import { BETA_INVITE_MAILTO } from "~/lib/beta";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Request beta access",
|
||||||
|
description:
|
||||||
|
"Learn who the Medscribe internal beta is for, what participants should expect, and how to request access without submitting health information.",
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectations = [
|
||||||
|
{
|
||||||
|
icon: ClipboardCheck,
|
||||||
|
title: "Review is part of testing",
|
||||||
|
body: "Check generated summaries, extracted medication fields, and the Know Me view before relying on them.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: MessageSquareText,
|
||||||
|
title: "Direct feedback",
|
||||||
|
body: "Report confusing language, incorrect extraction, notification behavior, and sharing friction.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: RotateCcw,
|
||||||
|
title: "Possible resets",
|
||||||
|
body: "This is an internal beta. Builds, local data, and setup may need to be reset as the product changes.",
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const testFocus = [
|
||||||
|
"Whether the record still feels current after several weeks",
|
||||||
|
"Medication-label edge cases and correction flow",
|
||||||
|
"Reminder permissions, timing, travel, and daylight-saving behavior",
|
||||||
|
"Caregiver pairing, encrypted export, and import comprehension",
|
||||||
|
"Whether the Know Me handoff is readable and appropriately cautious",
|
||||||
|
"Physical-device model performance and battery or thermal behavior",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export default function BetaPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section className="px-4 pt-16 pb-18 sm:px-6 sm:pt-22">
|
||||||
|
<div className="mx-auto grid max-w-6xl gap-12 lg:grid-cols-[1fr_0.8fr] lg:items-center">
|
||||||
|
<div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<Badge variant="success">{productRelease.stage}</Badge>
|
||||||
|
<Badge variant="outline">{productRelease.platforms}</Badge>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-foreground mt-6 max-w-3xl font-serif text-5xl leading-[1.02] font-semibold tracking-[-0.03em] sm:text-6xl">
|
||||||
|
Test the care context, not just the demo.
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground mt-6 max-w-2xl text-lg leading-relaxed sm:text-xl">
|
||||||
|
We are looking for people managing repeated visits or several
|
||||||
|
medications, especially family caregiver pairs willing to review
|
||||||
|
the record over time and tell us where it fails.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="border-border-soft bg-surface rounded-[2rem] border p-7 shadow-[0_24px_70px_rgba(15,23,42,0.08)] sm:p-9">
|
||||||
|
<div className="bg-primary-light text-primary flex size-12 items-center justify-center rounded-xl">
|
||||||
|
<Mail className="size-6" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-foreground mt-5 font-serif text-3xl font-semibold">
|
||||||
|
Request access by email
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-4 text-sm leading-relaxed">
|
||||||
|
The draft asks only for your platform, whether you are testing as
|
||||||
|
a patient, caregiver, or both, and whether you can test with
|
||||||
|
another person.
|
||||||
|
</p>
|
||||||
|
<Button asChild size="lg" className="mt-7 w-full sm:w-auto">
|
||||||
|
<a href={BETA_INVITE_MAILTO}>
|
||||||
|
Draft beta request
|
||||||
|
<ArrowRight className="size-4" aria-hidden="true" />
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
<p className="bg-surface-soft text-muted-foreground mt-5 rounded-xl p-4 text-xs leading-relaxed">
|
||||||
|
Do not send diagnoses, medication names, visit notes, transcripts,
|
||||||
|
or other health details. Email is not a medical-support or
|
||||||
|
emergency channel.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="border-border-soft border-y bg-[#f4f0e8]/75 px-4 py-20 sm:px-6 lg:py-24 dark:bg-[#151b25]">
|
||||||
|
<div className="mx-auto max-w-6xl">
|
||||||
|
<SectionLabel>A good fit</SectionLabel>
|
||||||
|
<h2 className="text-foreground mt-4 max-w-3xl font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||||
|
The beta needs real maintenance and handoff situations.
|
||||||
|
</h2>
|
||||||
|
<div className="mt-10 grid gap-4 md:grid-cols-3">
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
icon: Smartphone,
|
||||||
|
title: "Several moving pieces",
|
||||||
|
body: "You regularly coordinate visits, medication reminders, or follow-ups.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Users,
|
||||||
|
title: "A caregiver relationship",
|
||||||
|
body: "You can test a deliberate handoff with a trusted family caregiver.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Check,
|
||||||
|
title: "Comfort reviewing",
|
||||||
|
body: "You understand generated information can be wrong and will check it.",
|
||||||
|
},
|
||||||
|
].map((item) => {
|
||||||
|
const Icon = item.icon;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
key={item.title}
|
||||||
|
className="border-border-soft bg-surface rounded-2xl border p-6"
|
||||||
|
>
|
||||||
|
<Icon className="text-primary size-6" aria-hidden="true" />
|
||||||
|
<h3 className="text-foreground mt-5 font-serif text-2xl font-semibold">
|
||||||
|
{item.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-muted-foreground mt-3 text-sm leading-relaxed">
|
||||||
|
{item.body}
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="px-4 py-20 sm:px-6 lg:py-28">
|
||||||
|
<div className="mx-auto max-w-6xl">
|
||||||
|
<div className="grid gap-12 lg:grid-cols-[0.8fr_1.2fr]">
|
||||||
|
<div>
|
||||||
|
<SectionLabel>What to expect</SectionLabel>
|
||||||
|
<h2 className="text-foreground mt-4 font-serif text-4xl font-semibold tracking-tight">
|
||||||
|
This is collaborative product testing.
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-5 leading-relaxed">
|
||||||
|
Availability is controlled and continuity is not guaranteed.
|
||||||
|
Medscribe is not emergency care, diagnosis, dosing guidance, or
|
||||||
|
a replacement for your care team.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-4 sm:grid-cols-3">
|
||||||
|
{expectations.map((item) => {
|
||||||
|
const Icon = item.icon;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
key={item.title}
|
||||||
|
className="border-border-soft bg-surface rounded-2xl border p-5"
|
||||||
|
>
|
||||||
|
<Icon className="text-primary size-5" aria-hidden="true" />
|
||||||
|
<h3 className="text-foreground mt-4 font-serif text-xl font-semibold">
|
||||||
|
{item.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-muted-foreground mt-3 text-sm leading-relaxed">
|
||||||
|
{item.body}
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-16 rounded-[2rem] bg-[#0f4f4b] p-7 text-white sm:p-10">
|
||||||
|
<div className="grid gap-10 lg:grid-cols-[0.7fr_1.3fr]">
|
||||||
|
<div>
|
||||||
|
<p className="text-xs font-bold tracking-[0.14em] text-teal-100 uppercase">
|
||||||
|
Current test focus
|
||||||
|
</p>
|
||||||
|
<h2 className="mt-4 font-serif text-3xl font-semibold">
|
||||||
|
The questions we are trying to answer
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<ul className="grid gap-3 sm:grid-cols-2">
|
||||||
|
{testFocus.map((item) => (
|
||||||
|
<li
|
||||||
|
key={item}
|
||||||
|
className="flex gap-3 rounded-xl border border-white/15 bg-white/8 p-4 text-sm leading-relaxed text-teal-50/85"
|
||||||
|
>
|
||||||
|
<Check
|
||||||
|
className="mt-0.5 size-4 shrink-0 text-teal-100"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
{item}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="border-border-soft mt-12 flex flex-col items-start justify-between gap-5 border-t pt-10 sm:flex-row sm:items-center">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-foreground font-serif text-2xl font-semibold">
|
||||||
|
Want the technical boundaries first?
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-2 text-sm">
|
||||||
|
See what stays local, how sharing works, and the current
|
||||||
|
evidence.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button asChild variant="outline">
|
||||||
|
<Link href="/privacy">Read privacy & evidence</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
|
import { NorthwellPrintSummary } from "~/components/northwell-print-summary";
|
||||||
|
import { PrintButton } from "~/components/print-button";
|
||||||
|
import { SectionLabel } from "~/components/section-label";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Northwell executive summary",
|
||||||
|
robots: { index: false, follow: false, nocache: true },
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function NorthwellBriefPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NorthwellPrintSummary />
|
||||||
|
<div className="print-hidden mx-auto max-w-2xl px-4 py-16 text-center sm:px-6 lg:py-24">
|
||||||
|
<SectionLabel>Executive summary · Northwell Health</SectionLabel>
|
||||||
|
<h1 className="text-foreground mt-4 font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||||
|
The health-system executive summary
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground mx-auto mt-5 max-w-xl leading-relaxed">
|
||||||
|
This private, search-excluded route preserves the existing three-page
|
||||||
|
print document. Use Print PDF (or Cmd+P, then Save as PDF) to export
|
||||||
|
it.
|
||||||
|
</p>
|
||||||
|
<div className="mt-8 flex justify-center">
|
||||||
|
<PrintButton size="lg" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
|
import { CvsPrintSummary } from "~/components/cvs-print-summary";
|
||||||
|
import { PrintButton } from "~/components/print-button";
|
||||||
|
import { SectionLabel } from "~/components/section-label";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "CVS executive summary",
|
||||||
|
robots: { index: false, follow: false, nocache: true },
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CvsSummaryPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CvsPrintSummary />
|
||||||
|
<div className="print-hidden mx-auto max-w-2xl px-4 py-16 text-center sm:px-6 lg:py-24">
|
||||||
|
<SectionLabel>Executive summary · CVS Health</SectionLabel>
|
||||||
|
<h1 className="text-foreground mt-4 font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||||
|
The pharmacy executive summary
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground mx-auto mt-5 max-w-xl leading-relaxed">
|
||||||
|
This page is formatted as a two-page print document: the medication
|
||||||
|
record that moves adherence, keeps refills, and lowers cost of care.
|
||||||
|
Use Print PDF (or Cmd+P, then Save as PDF) to export it.
|
||||||
|
</p>
|
||||||
|
<div className="mt-8 flex justify-center">
|
||||||
|
<PrintButton size="lg" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ export const metadata: Metadata = {
|
|||||||
title: "Executive Summary",
|
title: "Executive Summary",
|
||||||
description:
|
description:
|
||||||
"One-page executive summary for Medscribe, a private medical context app for families managing complex care.",
|
"One-page executive summary for Medscribe, a private medical context app for families managing complex care.",
|
||||||
|
robots: { index: false, follow: false, nocache: true },
|
||||||
};
|
};
|
||||||
|
|
||||||
const TAGLINE =
|
const TAGLINE =
|
||||||
|
|||||||
@@ -0,0 +1,268 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import {
|
||||||
|
ArrowRight,
|
||||||
|
Camera,
|
||||||
|
Check,
|
||||||
|
FileText,
|
||||||
|
Keyboard,
|
||||||
|
Mic,
|
||||||
|
ShieldAlert,
|
||||||
|
} from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import { PhoneMockup } from "~/components/phone-mockup";
|
||||||
|
import { SectionLabel } from "~/components/section-label";
|
||||||
|
import { Badge } from "~/components/ui/badge";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
import { productClaims } from "~/content/product";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "How it works",
|
||||||
|
description:
|
||||||
|
"See how Medscribe turns visits and medication routines into one local record a person can review and carry.",
|
||||||
|
};
|
||||||
|
|
||||||
|
const inputs = [
|
||||||
|
{
|
||||||
|
icon: Keyboard,
|
||||||
|
title: "Type or paste",
|
||||||
|
body: "Start from notes you already have and keep the source close to the result.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Camera,
|
||||||
|
title: "Import a screenshot",
|
||||||
|
body: "Bring in a portal summary or prescription label, then correct extracted fields.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: FileText,
|
||||||
|
title: "Attach a document",
|
||||||
|
body: "Use a photo or document as the input to a local review workflow.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Mic,
|
||||||
|
title: "Record with consent",
|
||||||
|
body: "Optional visit audio requires consent and is transcribed on the device.",
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const boundaries = [
|
||||||
|
"It does not diagnose or recommend treatment.",
|
||||||
|
"It does not calculate, shift, or supply medication timing.",
|
||||||
|
"It does not replace a pharmacist, prescribing team, or emergency service.",
|
||||||
|
"It does not automatically synchronize a household in the background.",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export default function HowItWorksPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section className="px-4 pt-16 pb-18 sm:px-6 sm:pt-22">
|
||||||
|
<div className="mx-auto max-w-6xl">
|
||||||
|
<Badge variant="success">Current beta workflow</Badge>
|
||||||
|
<h1 className="text-foreground mt-6 max-w-4xl font-serif text-5xl leading-[1.02] font-semibold tracking-[-0.03em] sm:text-6xl">
|
||||||
|
One record, built through everyday use.
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground mt-6 max-w-2xl text-lg leading-relaxed sm:text-xl">
|
||||||
|
Medscribe is useful at a handoff only if ordinary moments keep the
|
||||||
|
picture current. Every generated result has a person in the review
|
||||||
|
loop.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="border-border-soft bg-surface/75 border-y px-4 py-18 sm:px-6 lg:py-22">
|
||||||
|
<div className="mx-auto max-w-6xl">
|
||||||
|
<SectionLabel>Start where the information is</SectionLabel>
|
||||||
|
<h2 className="text-foreground mt-4 max-w-3xl font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||||
|
Four ways in. One review checkpoint.
|
||||||
|
</h2>
|
||||||
|
<div className="mt-10 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
|
{inputs.map((item, index) => {
|
||||||
|
const Icon = item.icon;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
key={item.title}
|
||||||
|
className="border-border-soft bg-canvas rounded-2xl border p-6"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="bg-primary-light text-primary flex size-11 items-center justify-center rounded-xl">
|
||||||
|
<Icon className="size-5" aria-hidden="true" />
|
||||||
|
</span>
|
||||||
|
<span className="text-muted-foreground text-xs font-bold">
|
||||||
|
0{index + 1}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-foreground mt-5 font-serif text-2xl font-semibold">
|
||||||
|
{item.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-muted-foreground mt-3 text-sm leading-relaxed">
|
||||||
|
{item.body}
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="border-primary/20 bg-primary-light/45 mt-6 flex gap-3 rounded-2xl border p-5">
|
||||||
|
<Check
|
||||||
|
className="text-primary mt-0.5 size-5 shrink-0"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<p className="text-foreground text-sm leading-relaxed">
|
||||||
|
The person reviews summaries and extracted medication fields.
|
||||||
|
Medscribe does not silently turn model output into an
|
||||||
|
authoritative medical record.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="px-4 py-20 sm:px-6 lg:py-28">
|
||||||
|
<div className="mx-auto max-w-6xl space-y-24">
|
||||||
|
<div className="grid gap-12 lg:grid-cols-2 lg:items-center">
|
||||||
|
<div>
|
||||||
|
<SectionLabel>Review what changed</SectionLabel>
|
||||||
|
<h2 className="text-foreground mt-4 font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||||
|
A summary is a draft, not a verdict.
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-5 leading-relaxed">
|
||||||
|
Supported transcription and summarization run locally. The visit
|
||||||
|
remains available beside the summary so a person can check
|
||||||
|
follow-ups and wording.
|
||||||
|
</p>
|
||||||
|
<p className="border-border-soft bg-surface text-muted-foreground mt-5 rounded-2xl border p-5 text-sm leading-relaxed">
|
||||||
|
<strong className="text-foreground">What comes next:</strong> a
|
||||||
|
structured review inbox for accepting, correcting, or dismissing
|
||||||
|
changes inferred from a visit.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-primary-light/45 justify-self-center rounded-[2.5rem] p-7">
|
||||||
|
<PhoneMockup
|
||||||
|
src="/screenshots/appointment-summary.png"
|
||||||
|
alt="Synthetic Medscribe beta visit summary"
|
||||||
|
width={235}
|
||||||
|
/>
|
||||||
|
<p className="text-muted-foreground mt-4 text-center text-xs">
|
||||||
|
Synthetic demo data · beta interface
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-12 lg:grid-cols-2 lg:items-center">
|
||||||
|
<div className="justify-self-center rounded-[2.5rem] bg-[#f4f0e8] p-7 lg:order-1 dark:bg-[#151b25]">
|
||||||
|
<PhoneMockup
|
||||||
|
src="/screenshots/medication-detail.png"
|
||||||
|
alt="Synthetic Medscribe beta medication detail"
|
||||||
|
width={235}
|
||||||
|
/>
|
||||||
|
<p className="text-muted-foreground mt-4 text-center text-xs">
|
||||||
|
Synthetic demo data · beta interface
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="lg:order-2">
|
||||||
|
<SectionLabel>Maintain the medication picture</SectionLabel>
|
||||||
|
<h2 className="text-foreground mt-4 font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||||
|
Facts and schedules carry their boundary with them.
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-5 leading-relaxed">
|
||||||
|
{productClaims.medicationFacts.publicCopy} Schedules can record
|
||||||
|
whether they are fixed-time or as-needed, the times supplied by
|
||||||
|
the person's source, confirmation, and timezone.
|
||||||
|
</p>
|
||||||
|
<p className="text-muted-foreground mt-5 text-sm leading-relaxed">
|
||||||
|
Medscribe records timing supplied by a label, pharmacist, or
|
||||||
|
prescribing team. It does not calculate or shift medication
|
||||||
|
times.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-12 lg:grid-cols-2 lg:items-center">
|
||||||
|
<div>
|
||||||
|
<SectionLabel>Prepare the handoff</SectionLabel>
|
||||||
|
<h2 className="text-foreground mt-4 font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||||
|
Know Me is the view. Sharing is a separate choice.
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-5 leading-relaxed">
|
||||||
|
Open the local Know Me screen when someone needs a concise
|
||||||
|
picture. For a caregiver, pair devices, compare a safety number,
|
||||||
|
review the record, and manually export an encrypted snapshot.
|
||||||
|
</p>
|
||||||
|
<p className="text-muted-foreground mt-5 text-sm leading-relaxed">
|
||||||
|
The current import path does not yet require the sender to be in
|
||||||
|
the recipient's active trust list, so recipients must
|
||||||
|
verify the sender out of band. Revocation prevents a future
|
||||||
|
local export; it cannot invalidate already-exported ciphertext
|
||||||
|
or erase a copy the recipient imported.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-primary-light/45 justify-self-center rounded-[2.5rem] p-7">
|
||||||
|
<PhoneMockup
|
||||||
|
src="/screenshots/emergency-record.png"
|
||||||
|
alt="Synthetic Medscribe beta Know Me handoff"
|
||||||
|
width={235}
|
||||||
|
/>
|
||||||
|
<p className="text-muted-foreground mt-4 text-center text-xs">
|
||||||
|
Synthetic demo data · beta interface
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="border-border-soft border-t bg-[#f4f0e8]/75 px-4 py-20 sm:px-6 dark:bg-[#151b25]">
|
||||||
|
<div className="mx-auto grid max-w-6xl gap-12 lg:grid-cols-2">
|
||||||
|
<div>
|
||||||
|
<div className="bg-primary-light text-primary flex size-12 items-center justify-center rounded-xl">
|
||||||
|
<ShieldAlert className="size-6" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-foreground mt-5 font-serif text-4xl font-semibold tracking-tight">
|
||||||
|
What Medscribe does not do
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-4 leading-relaxed">
|
||||||
|
A useful boundary is part of the product, not fine print after it.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ul className="space-y-3">
|
||||||
|
{boundaries.map((item) => (
|
||||||
|
<li
|
||||||
|
key={item}
|
||||||
|
className="border-border-soft bg-surface text-foreground flex gap-3 rounded-2xl border p-4 text-sm leading-relaxed"
|
||||||
|
>
|
||||||
|
<Check
|
||||||
|
className="text-primary mt-0.5 size-5 shrink-0"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
{item}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="px-4 py-20 sm:px-6">
|
||||||
|
<div className="mx-auto flex max-w-6xl flex-col items-start justify-between gap-7 rounded-[2rem] bg-[#0f4f4b] px-7 py-10 text-white sm:px-10 md:flex-row md:items-center">
|
||||||
|
<div>
|
||||||
|
<h2 className="font-serif text-3xl font-semibold">
|
||||||
|
See the trust boundary task by task.
|
||||||
|
</h2>
|
||||||
|
<p className="mt-2 text-sm leading-relaxed text-teal-50/75">
|
||||||
|
Read what stays local, what can use a network, and what remains
|
||||||
|
unfinished.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
asChild
|
||||||
|
size="lg"
|
||||||
|
variant="outline"
|
||||||
|
className="border-white/20 bg-white text-[#0f4f4b] hover:bg-teal-50"
|
||||||
|
>
|
||||||
|
<Link href="/privacy">
|
||||||
|
Privacy & evidence
|
||||||
|
<ArrowRight className="size-4" aria-hidden="true" />
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ export const metadata: Metadata = {
|
|||||||
title: "Solution Landscape",
|
title: "Solution Landscape",
|
||||||
description:
|
description:
|
||||||
"How the medical-context space is divided today, and the combined job no existing product owns.",
|
"How the medical-context space is divided today, and the combined job no existing product owns.",
|
||||||
|
robots: { index: false, follow: false, nocache: true },
|
||||||
};
|
};
|
||||||
|
|
||||||
const INTRO =
|
const INTRO =
|
||||||
|
|||||||
@@ -7,9 +7,15 @@ export default function MarketingLayout({
|
|||||||
}: Readonly<{ children: React.ReactNode }>) {
|
}: Readonly<{ children: React.ReactNode }>) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<a
|
||||||
|
href="#main-content"
|
||||||
|
className="print-hidden bg-primary-button fixed top-2 left-2 z-[100] -translate-y-20 rounded-lg px-4 py-2 text-sm font-semibold text-white transition-transform focus:translate-y-0"
|
||||||
|
>
|
||||||
|
Skip to content
|
||||||
|
</a>
|
||||||
<ScreenBackground />
|
<ScreenBackground />
|
||||||
<SiteHeader />
|
<SiteHeader />
|
||||||
<main>{children}</main>
|
<main id="main-content">{children}</main>
|
||||||
<SiteFooter />
|
<SiteFooter />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,229 +1,522 @@
|
|||||||
import { HeartPulse, Layers, ShieldCheck, Users } from "lucide-react";
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
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 {
|
import {
|
||||||
Card,
|
ArrowRight,
|
||||||
CardDescription,
|
Check,
|
||||||
CardHeader,
|
ContactRound,
|
||||||
CardTitle,
|
KeyRound,
|
||||||
} from "~/components/ui/card";
|
LockKeyhole,
|
||||||
|
ScanLine,
|
||||||
|
ShieldCheck,
|
||||||
|
Smartphone,
|
||||||
|
} from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
const fit = [
|
import { ProductDevice } from "~/components/product-device";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
import { productClaims, productRelease } from "~/content/product";
|
||||||
|
import { BETA_INVITE_MAILTO } from "~/lib/beta";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Carry the context when care gets complicated",
|
||||||
|
description:
|
||||||
|
"Medscribe is a private, patient-controlled beta that helps keep visits, medications, and critical context together on your device.",
|
||||||
|
};
|
||||||
|
|
||||||
|
const visitMoments = [
|
||||||
{
|
{
|
||||||
icon: Layers,
|
number: "01",
|
||||||
title: "Complements Epic, doesn't compete",
|
eyebrow: "Visits stay findable",
|
||||||
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.",
|
title: "The visit has a place to land.",
|
||||||
|
body: "Keep the recording, transcript, and reviewed summary together instead of reconstructing the appointment from memory.",
|
||||||
|
image: "/screenshots/pitch/visits-natural.webp",
|
||||||
|
alt: "Medscribe Visits screen in a natural titanium iPhone product bezel",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Users,
|
number: "02",
|
||||||
title: "Keeps patients active in their own care",
|
eyebrow: "Words stay checkable",
|
||||||
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.",
|
title: "Go back to what was actually said.",
|
||||||
|
body: "Search the local transcript and compare it with the generated summary before anything becomes part of the record you rely on.",
|
||||||
|
image: "/screenshots/pitch/transcript-black.webp",
|
||||||
|
alt: "Medscribe visit transcript in a black titanium iPhone product bezel",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: HeartPulse,
|
number: "03",
|
||||||
title: "Patient-centered where it counts",
|
eyebrow: "The next step stays visible",
|
||||||
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.",
|
title: "Review the useful part. Keep the uncertainty.",
|
||||||
|
body: "Medications, follow-ups, diagnoses, and instructions are prepared on-device. Suggestions remain suggestions until a person checks them.",
|
||||||
|
image: "/screenshots/pitch/summary-natural.webp",
|
||||||
|
alt: "Medscribe appointment summary in a natural titanium iPhone product bezel",
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const proofStats = [
|
||||||
|
{
|
||||||
|
value: productRelease.drugFacts.toLocaleString("en-US"),
|
||||||
|
label: "bundled medication records",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: productRelease.rxNormTerms.toLocaleString("en-US"),
|
||||||
|
label: "RxNorm terms for matching",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: productRelease.interactionPairs.toLocaleString("en-US"),
|
||||||
|
label: "known interaction pairs",
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const privacyPrinciples = [
|
||||||
|
{
|
||||||
|
icon: Smartphone,
|
||||||
|
title: "Your working record stays local",
|
||||||
|
body: productClaims.localRecord.publicCopy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: LockKeyhole,
|
||||||
|
title: "AI runs on the device",
|
||||||
|
body: productClaims.localAi.publicCopy,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: ShieldCheck,
|
icon: ShieldCheck,
|
||||||
title: "Private by architecture",
|
title: "Facts do not come from model memory",
|
||||||
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.",
|
body: productClaims.medicationFacts.publicCopy,
|
||||||
},
|
},
|
||||||
];
|
] as const;
|
||||||
|
|
||||||
|
const betaFit = [
|
||||||
|
"Managing several medications or repeated visits",
|
||||||
|
"Coordinating care with a family caregiver",
|
||||||
|
"Willing to review generated information and report errors",
|
||||||
|
] as const;
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
return (
|
return (
|
||||||
<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 */}
|
<section className="relative isolate min-h-[calc(100svh-4rem)] overflow-hidden px-4 pt-14 sm:px-6 sm:pt-20 lg:pt-24">
|
||||||
<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">
|
<div className="bg-primary-light/55 absolute top-0 left-1/2 -z-10 h-[38rem] w-[68rem] max-w-[120vw] -translate-x-1/2 rounded-full blur-3xl" />
|
||||||
<PhoneMockup
|
<div className="mx-auto max-w-7xl text-center">
|
||||||
src="/screenshots/home.png"
|
<div className="animate-fade-in-up flex items-center justify-center gap-3">
|
||||||
alt="Medscribe home screen"
|
<span className="bg-success-light text-success rounded-full px-3 py-1.5 text-xs font-bold tracking-[0.12em] uppercase">
|
||||||
width={200}
|
{productRelease.stage}
|
||||||
priority
|
</span>
|
||||||
/>
|
<span className="text-muted-foreground text-xs font-semibold tracking-[0.12em] uppercase">
|
||||||
</div>
|
{productRelease.platforms}
|
||||||
|
</span>
|
||||||
<div className="space-y-10 lg:space-y-12">
|
</div>
|
||||||
{/* Origin */}
|
<h1 className="animate-fade-in-up-delay-1 text-foreground mx-auto mt-7 max-w-6xl font-serif text-[clamp(3.6rem,9vw,8.5rem)] leading-[0.86] font-semibold tracking-[-0.055em]">
|
||||||
<section className="animate-fade-in-up space-y-5">
|
Carry the context.
|
||||||
<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>
|
</h1>
|
||||||
<div className="text-muted-foreground space-y-4 text-base leading-relaxed sm:text-lg">
|
<p className="animate-fade-in-up-delay-2 text-muted-foreground mx-auto mt-7 max-w-2xl text-lg leading-relaxed sm:text-xl lg:text-2xl">
|
||||||
<p>
|
Visits, medications, and the critical details between them—kept
|
||||||
A grandfather with three adult children, each living twenty to
|
together on the device you carry.
|
||||||
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>
|
|
||||||
</div>
|
|
||||||
<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">Complements Epic</Badge>
|
|
||||||
<Badge variant="outline">iOS · Android beta</Badge>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* 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>
|
</p>
|
||||||
<div className="border-primary/30 space-y-2 border-l-2 pl-4">
|
<div className="animate-fade-in-up-delay-3 mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
|
||||||
<p className="text-foreground text-sm font-medium">
|
<Button asChild size="lg">
|
||||||
How the record stays current (live in beta today)
|
<Link href="/beta">
|
||||||
</p>
|
Request beta access
|
||||||
<ul className="text-muted-foreground space-y-1.5 text-sm leading-relaxed">
|
<ArrowRight className="size-4" aria-hidden="true" />
|
||||||
<li>
|
</Link>
|
||||||
<span className="text-foreground">Visit summaries: </span>type
|
</Button>
|
||||||
or paste notes, import MyChart and portal after-visit summaries
|
<Button asChild size="lg" variant="outline">
|
||||||
from a screenshot, or attach photos and documents; optional
|
<Link href="/how-it-works">See how it works</Link>
|
||||||
consent-gated audio recording for patients who want it. Any path
|
</Button>
|
||||||
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>
|
</div>
|
||||||
|
|
||||||
{/* The gap Epic's own tools can't close */}
|
<div className="relative mx-auto mt-12 h-[36rem] max-w-5xl sm:h-[46rem] lg:mt-8 lg:h-[54rem]">
|
||||||
<section className="animate-fade-in-up space-y-3">
|
<div className="absolute top-24 left-1/2 w-[17rem] -translate-x-[86%] -rotate-6 sm:top-28 sm:w-[22rem] lg:top-32 lg:w-[26rem]">
|
||||||
<SectionLabel>Where the picture breaks</SectionLabel>
|
<ProductDevice
|
||||||
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
src="/screenshots/pitch/summary-natural.webp"
|
||||||
Epic knows what Epic sees
|
alt="Medscribe visit summary in a natural titanium iPhone product bezel"
|
||||||
</h2>
|
sizes="(min-width: 1024px) 416px, (min-width: 640px) 352px, 272px"
|
||||||
<p className="text-muted-foreground leading-relaxed">
|
priority
|
||||||
MyChart is excellent inside the Epic walls. It puts the chart on the
|
/>
|
||||||
patient's phone, links their Epic portals through MyChart
|
</div>
|
||||||
Central, and lets them share with a caregiver or a provider. What no
|
<div className="absolute top-0 left-1/2 z-10 w-[18rem] -translate-x-[14%] rotate-5 sm:w-[23rem] lg:w-[28rem]">
|
||||||
institutional record can be is the whole picture: the urgent care,
|
<ProductDevice
|
||||||
the community specialist, the facility that isn't on Epic; the
|
src="/screenshots/pitch/home-black.webp"
|
||||||
changes between visits; the context a patient keeps themselves. Epic
|
alt="Medscribe home screen in a black titanium iPhone product bezel"
|
||||||
aggregates Epic. And when it matters most, at an out-of-network ER,
|
sizes="(min-width: 1024px) 448px, (min-width: 640px) 368px, 288px"
|
||||||
or when the patient can't generate a share code, the record
|
priority
|
||||||
that's actually there is the one they already carry.
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="text-muted-foreground absolute inset-x-0 bottom-5 text-center text-xs font-medium tracking-wide sm:bottom-10">
|
||||||
|
Synthetic demo data · beta interface
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
{/* What makes it different */}
|
<section className="pitch-lazy border-border-soft bg-surface/80 flex min-h-[78svh] items-center border-y px-4 py-24 sm:px-6 lg:py-32">
|
||||||
<section className="animate-fade-in-up space-y-3">
|
<div className="mx-auto max-w-6xl text-center">
|
||||||
<SectionLabel>What makes it different</SectionLabel>
|
<p className="text-primary text-xs font-bold tracking-[0.2em] uppercase">
|
||||||
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
The missing picture
|
||||||
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>
|
</p>
|
||||||
</section>
|
<h2 className="text-foreground mx-auto mt-7 max-w-5xl font-serif text-[clamp(2.8rem,7vw,6.5rem)] leading-[0.98] font-semibold tracking-[-0.045em]">
|
||||||
|
Care happens in fragments. Your context shouldn't.
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground mx-auto mt-8 max-w-2xl text-lg leading-relaxed sm:text-xl">
|
||||||
|
One instruction lives in a portal. A medication label sits in a
|
||||||
|
cabinet. The latest change lives in someone's memory. Medscribe
|
||||||
|
is the patient-held thread between those moments.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
{/* Why it fits Northwell: clear float so the card grid spans full width */}
|
<section className="pitch-lazy px-4 py-24 sm:px-6 lg:py-36">
|
||||||
<section className="animate-fade-in-up clear-both space-y-6">
|
<div className="mx-auto grid max-w-7xl gap-16 lg:grid-cols-[0.78fr_1.22fr] lg:gap-24">
|
||||||
<div className="space-y-2">
|
<div className="lg:sticky lg:top-28 lg:h-fit">
|
||||||
<SectionLabel>Why it fits Northwell</SectionLabel>
|
<p className="text-primary text-xs font-bold tracking-[0.2em] uppercase">
|
||||||
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
After the visit
|
||||||
Built to strengthen the Epic investment, not compete with it
|
</p>
|
||||||
|
<h2 className="text-foreground mt-6 max-w-xl font-serif text-5xl leading-[0.98] font-semibold tracking-[-0.04em] sm:text-6xl">
|
||||||
|
Turn what happened into something you can check.
|
||||||
</h2>
|
</h2>
|
||||||
|
<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
|
||||||
|
optional consent-gated recording. The work happens locally. You
|
||||||
|
decide what is useful.
|
||||||
|
</p>
|
||||||
|
<div className="border-primary/25 bg-primary-light/35 mt-8 max-w-lg rounded-2xl border p-5">
|
||||||
|
<p className="text-foreground text-sm leading-relaxed">
|
||||||
|
<strong className="font-semibold">The safety loop:</strong>{" "}
|
||||||
|
review generated information before acting. Medscribe does not
|
||||||
|
diagnose, prescribe, or replace clinical verification.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
|
||||||
{fit.map((item) => {
|
<ol className="space-y-8 sm:space-y-12">
|
||||||
const Icon = item.icon;
|
{visitMoments.map((moment) => (
|
||||||
|
<li
|
||||||
|
key={moment.number}
|
||||||
|
className="border-border-soft bg-surface relative overflow-hidden rounded-[2.5rem] border px-5 pt-8 shadow-[0_30px_90px_rgba(15,23,42,0.08)] sm:px-10 sm:pt-10 dark:shadow-[0_30px_90px_rgba(0,0,0,0.25)]"
|
||||||
|
>
|
||||||
|
<div className="relative z-10 max-w-xl">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<span className="bg-primary-light text-primary flex size-9 items-center justify-center rounded-full text-xs font-bold">
|
||||||
|
{moment.number}
|
||||||
|
</span>
|
||||||
|
<p className="text-primary text-xs font-bold tracking-[0.16em] uppercase">
|
||||||
|
{moment.eyebrow}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-foreground mt-5 font-serif text-3xl font-semibold tracking-[-0.025em] sm:text-4xl">
|
||||||
|
{moment.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-muted-foreground mt-4 max-w-lg leading-relaxed">
|
||||||
|
{moment.body}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-primary-light/45 absolute -right-36 bottom-14 -z-0 size-96 rounded-full blur-3xl" />
|
||||||
|
<div className="relative mx-auto mt-10 w-[min(72vw,21rem)] translate-y-14">
|
||||||
|
<ProductDevice
|
||||||
|
src={moment.image}
|
||||||
|
alt={moment.alt}
|
||||||
|
sizes="(min-width: 1024px) 336px, (min-width: 640px) 320px, 72vw"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="pitch-lazy relative isolate overflow-hidden bg-[#082f2c] px-4 py-24 text-white sm:px-6 lg:py-36">
|
||||||
|
<div className="absolute inset-0 -z-10 bg-[radial-gradient(circle_at_78%_18%,rgba(45,212,191,0.25),transparent_32%),radial-gradient(circle_at_16%_80%,rgba(20,184,166,0.15),transparent_28%)]" />
|
||||||
|
<div className="mx-auto max-w-7xl">
|
||||||
|
<div className="mx-auto max-w-4xl text-center">
|
||||||
|
<p className="text-xs font-bold tracking-[0.2em] text-teal-200 uppercase">
|
||||||
|
Between visits
|
||||||
|
</p>
|
||||||
|
<h2 className="mt-6 font-serif text-[clamp(3rem,7vw,6.7rem)] leading-[0.94] font-semibold tracking-[-0.045em]">
|
||||||
|
Keep the medication picture from going stale.
|
||||||
|
</h2>
|
||||||
|
<p className="mx-auto mt-8 max-w-2xl text-lg leading-relaxed text-teal-50/75 sm:text-xl">
|
||||||
|
The list, the label, the schedule, and the latest confirmation
|
||||||
|
belong in the same view—not scattered across paper and memory.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-16 grid gap-14 lg:grid-cols-2 lg:items-center lg:gap-6">
|
||||||
|
<div className="relative mx-auto h-[42rem] w-full max-w-2xl sm:h-[50rem] lg:h-[56rem]">
|
||||||
|
<div className="absolute top-20 left-[4%] w-[17rem] -rotate-6 sm:w-[21rem] lg:w-[23rem]">
|
||||||
|
<ProductDevice
|
||||||
|
src="/screenshots/pitch/medication-detail-natural.webp"
|
||||||
|
alt="Medscribe medication detail in a natural titanium iPhone product bezel"
|
||||||
|
sizes="(min-width: 1024px) 368px, (min-width: 640px) 336px, 272px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="absolute top-0 right-[2%] z-10 w-[18rem] rotate-5 sm:w-[22rem] lg:w-[24rem]">
|
||||||
|
<ProductDevice
|
||||||
|
src="/screenshots/pitch/medications-black.webp"
|
||||||
|
alt="Medscribe medication list in a black titanium iPhone product bezel"
|
||||||
|
sizes="(min-width: 1024px) 384px, (min-width: 640px) 352px, 288px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-px overflow-hidden rounded-[2rem] border border-white/12 bg-white/12">
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
title: "Show where a schedule came from",
|
||||||
|
body: "Schedules can carry their source, confirmation, timezone, and freshness state.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Keep facts deterministic",
|
||||||
|
body: productClaims.medicationFacts.publicCopy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Make uncertainty visible",
|
||||||
|
body: "Missing or stale details stay visible so the person or clinician can verify them.",
|
||||||
|
},
|
||||||
|
].map((item) => (
|
||||||
|
<article
|
||||||
|
key={item.title}
|
||||||
|
className="bg-[#0b3b37]/90 p-7 sm:p-9"
|
||||||
|
>
|
||||||
|
<Check className="size-5 text-teal-200" aria-hidden="true" />
|
||||||
|
<h3 className="mt-5 font-serif text-2xl font-semibold sm:text-3xl">
|
||||||
|
{item.title}
|
||||||
|
</h3>
|
||||||
|
<p className="mt-3 leading-relaxed text-teal-50/70">
|
||||||
|
{item.body}
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="pitch-lazy bg-[#f0ece4] px-4 py-24 sm:px-6 lg:py-36 dark:bg-[#111827]">
|
||||||
|
<div className="mx-auto grid max-w-7xl items-center gap-14 lg:grid-cols-[1fr_0.9fr] lg:gap-24">
|
||||||
|
<div className="order-2 lg:order-1">
|
||||||
|
<p className="text-primary text-xs font-bold tracking-[0.2em] uppercase">
|
||||||
|
Ask from the record
|
||||||
|
</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]">
|
||||||
|
Better questions begin with your context.
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-8 max-w-xl text-lg leading-relaxed sm:text-xl">
|
||||||
|
Ask about a visit or medication without uploading the record to a
|
||||||
|
remote model. Answers are grounded in local history and bundled
|
||||||
|
facts, with the boundary kept in view.
|
||||||
|
</p>
|
||||||
|
<p className="border-primary/35 text-muted-foreground mt-7 max-w-xl border-l-2 pl-5 text-sm leading-relaxed">
|
||||||
|
Informational only. Medication changes still belong with a
|
||||||
|
clinician or pharmacist.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="order-1 mx-auto w-[min(78vw,27rem)] lg:order-2">
|
||||||
|
<ProductDevice
|
||||||
|
src="/screenshots/pitch/ask-black.webp"
|
||||||
|
alt="Medscribe Ask response grounded in local health context, shown in a black titanium iPhone product bezel"
|
||||||
|
sizes="(min-width: 1024px) 432px, (min-width: 640px) 384px, 78vw"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="pitch-lazy overflow-hidden px-4 py-24 sm:px-6 lg:py-36">
|
||||||
|
<div className="mx-auto max-w-7xl">
|
||||||
|
<div className="grid items-center gap-16 lg:grid-cols-[0.92fr_1.08fr] lg:gap-24">
|
||||||
|
<div className="relative mx-auto w-[min(80vw,28rem)]">
|
||||||
|
<div className="bg-primary-light/70 absolute inset-[12%_-12%_8%] -z-10 rounded-[4rem] blur-2xl" />
|
||||||
|
<ProductDevice
|
||||||
|
src="/screenshots/pitch/know-me-natural.webp"
|
||||||
|
alt="Medscribe Know Me handoff record in a natural titanium iPhone product bezel"
|
||||||
|
sizes="(min-width: 1024px) 448px, (min-width: 640px) 400px, 80vw"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-primary text-xs font-bold tracking-[0.2em] uppercase">
|
||||||
|
At the handoff
|
||||||
|
</p>
|
||||||
|
<h2 className="text-foreground mt-6 max-w-2xl font-serif text-[clamp(3.2rem,6vw,6.2rem)] leading-[0.95] font-semibold tracking-[-0.045em]">
|
||||||
|
Bring the picture when the chart is incomplete.
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-8 max-w-xl text-lg leading-relaxed sm:text-xl">
|
||||||
|
Know Me gathers selected medications, allergies, conditions,
|
||||||
|
baseline, and contacts into one readable snapshot. Freshness
|
||||||
|
signals show what may need another look.
|
||||||
|
</p>
|
||||||
|
<div className="mt-9 grid gap-4 sm:grid-cols-2">
|
||||||
|
<div className="border-border-soft bg-surface rounded-2xl border p-5">
|
||||||
|
<ScanLine
|
||||||
|
className="text-primary size-6"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<p className="text-foreground mt-4 font-semibold">
|
||||||
|
Quick handoff
|
||||||
|
</p>
|
||||||
|
<p className="text-muted-foreground mt-2 text-sm leading-relaxed">
|
||||||
|
Show or share a selected, readable snapshot.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="border-border-soft bg-surface rounded-2xl border p-5">
|
||||||
|
<ContactRound
|
||||||
|
className="text-primary size-6"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<p className="text-foreground mt-4 font-semibold">
|
||||||
|
Caregiver copy
|
||||||
|
</p>
|
||||||
|
<p className="text-muted-foreground mt-2 text-sm leading-relaxed">
|
||||||
|
Manually export an encrypted copy for a paired recipient.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-24 overflow-hidden rounded-[2.5rem] bg-[#0f4f4b] 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>
|
||||||
|
<KeyRound className="size-7 text-teal-200" aria-hidden="true" />
|
||||||
|
<h3 className="mt-5 font-serif text-4xl leading-tight font-semibold sm:text-5xl">
|
||||||
|
Share a snapshot. Not an invisible feed.
|
||||||
|
</h3>
|
||||||
|
<p className="mt-5 max-w-xl leading-relaxed text-teal-50/75">
|
||||||
|
Pair by QR code, compare a safety number, and choose when to
|
||||||
|
export. The caregiver imports a local copy on their device.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ol className="grid gap-3 sm:grid-cols-3">
|
||||||
|
{[
|
||||||
|
["1", "You review", "Choose the record to send"],
|
||||||
|
["2", "Encrypted copy", "Protected for the recipient key"],
|
||||||
|
["3", "They import", "A local, readable snapshot"],
|
||||||
|
].map(([number, title, body]) => (
|
||||||
|
<li
|
||||||
|
key={number}
|
||||||
|
className="rounded-2xl border border-white/15 bg-white/8 p-5"
|
||||||
|
>
|
||||||
|
<span className="flex size-8 items-center justify-center rounded-full bg-white/12 text-xs font-bold">
|
||||||
|
{number}
|
||||||
|
</span>
|
||||||
|
<p className="mt-8 font-serif text-xl font-semibold">
|
||||||
|
{title}
|
||||||
|
</p>
|
||||||
|
<p className="mt-2 text-sm leading-relaxed text-teal-50/65">
|
||||||
|
{body}
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
<p className="mt-8 border-t border-white/12 pt-6 text-xs leading-relaxed text-teal-50/60">
|
||||||
|
Manual today—not live sync or a household cloud account.
|
||||||
|
Import-side sender trust enforcement is still being hardened;
|
||||||
|
revocation cannot recall a copy already exported.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="pitch-lazy relative isolate overflow-hidden bg-[#070d15] px-4 py-24 text-white sm:px-6 lg:py-36">
|
||||||
|
<div className="absolute inset-0 -z-10 bg-[radial-gradient(circle_at_50%_0%,rgba(45,212,191,0.13),transparent_36%)]" />
|
||||||
|
<div className="mx-auto max-w-7xl">
|
||||||
|
<div className="mx-auto max-w-4xl text-center">
|
||||||
|
<p className="text-xs font-bold tracking-[0.2em] text-teal-300 uppercase">
|
||||||
|
A precise privacy boundary
|
||||||
|
</p>
|
||||||
|
<h2 className="mt-6 font-serif text-[clamp(3.2rem,7vw,7rem)] leading-[0.92] font-semibold tracking-[-0.05em]">
|
||||||
|
Local changes what you have to trust.
|
||||||
|
</h2>
|
||||||
|
<p className="mx-auto mt-8 max-w-2xl text-lg leading-relaxed text-slate-300 sm:text-xl">
|
||||||
|
The working record and supported AI tasks stay on the device.
|
||||||
|
Medication facts come from bundled references, not a model trying
|
||||||
|
to remember medicine.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<dl className="mt-20 grid border-y border-white/12 md:grid-cols-3 md:divide-x md:divide-white/12">
|
||||||
|
{proofStats.map((stat) => (
|
||||||
|
<div
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
<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">
|
||||||
|
{stat.value}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<div className="mt-16 grid gap-4 md:grid-cols-3">
|
||||||
|
{privacyPrinciples.map((principle) => {
|
||||||
|
const Icon = principle.icon;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card key={item.title} className="card-hover">
|
<article
|
||||||
<CardHeader>
|
key={principle.title}
|
||||||
<div className="bg-primary-light flex size-10 items-center justify-center rounded-[10px]">
|
className="rounded-[1.75rem] border border-white/12 bg-white/[0.045] p-7"
|
||||||
<Icon className="text-primary size-5" />
|
>
|
||||||
</div>
|
<Icon className="size-6 text-teal-200" aria-hidden="true" />
|
||||||
<CardTitle className="text-lg">{item.title}</CardTitle>
|
<h3 className="mt-8 font-serif text-2xl font-semibold">
|
||||||
<CardDescription>{item.body}</CardDescription>
|
{principle.title}
|
||||||
</CardHeader>
|
</h3>
|
||||||
</Card>
|
<p className="mt-4 text-sm leading-relaxed text-slate-400">
|
||||||
|
{principle.body}
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Who's behind it + the ask */}
|
<div className="mt-10 text-center">
|
||||||
<section className="animate-fade-in-up">
|
<Button asChild variant="secondary">
|
||||||
<Card className="border-primary-button/20 bg-primary-button overflow-hidden text-white">
|
<Link href="/privacy">
|
||||||
<CardHeader className="gap-4">
|
Read privacy & evidence
|
||||||
<div className="space-y-3">
|
<ArrowRight className="size-4" aria-hidden="true" />
|
||||||
<CardTitle className="text-white">
|
</Link>
|
||||||
Who's behind it
|
</Button>
|
||||||
</CardTitle>
|
</div>
|
||||||
<CardDescription className="leading-relaxed text-white/80">
|
</div>
|
||||||
Sean O'Connor, a Bucknell computer science and engineering
|
</section>
|
||||||
graduate, built Medscribe after watching his own family try to
|
|
||||||
hold this together for his grandfather, when the record
|
<section className="pitch-lazy flex min-h-[82svh] items-center px-4 py-24 sm:px-6 lg:py-32">
|
||||||
scattered exactly at the moment it mattered most. It is live in
|
<div className="mx-auto w-full max-w-6xl overflow-hidden rounded-[3rem] border border-teal-900/10 bg-[linear-gradient(145deg,#ccfbf1_0%,#f0fdfa_44%,#ffffff_100%)] px-6 py-16 text-center shadow-[0_35px_110px_rgba(15,118,110,0.15)] sm:px-10 lg:px-20 lg:py-24 dark:border-teal-300/10 dark:bg-[linear-gradient(145deg,#134e4a_0%,#0f2930_48%,#111827_100%)]">
|
||||||
beta on iOS and Android today.
|
<p className="text-primary text-xs font-bold tracking-[0.2em] uppercase">
|
||||||
</CardDescription>
|
The right beta participant
|
||||||
<CardDescription className="leading-relaxed text-white/80">
|
</p>
|
||||||
The ask is a short conversation about whether a patient-owned
|
<h2 className="text-foreground mx-auto mt-6 max-w-4xl font-serif text-[clamp(3.2rem,7vw,7rem)] leading-[0.92] font-semibold tracking-[-0.05em]">
|
||||||
record that spans the systems Epic can't reach, and feeds
|
Help build the record people can actually carry.
|
||||||
that context back into Northwell's Epic, is worth
|
</h2>
|
||||||
exploring together.
|
<p className="text-muted-foreground mx-auto mt-8 max-w-2xl text-lg leading-relaxed sm:text-xl">
|
||||||
</CardDescription>
|
This is an internal beta. Expect close feedback, manual review,
|
||||||
</div>
|
rough edges, and possible resets—not guaranteed continuity.
|
||||||
<Button
|
</p>
|
||||||
asChild
|
<ul className="mx-auto mt-9 grid max-w-4xl gap-3 text-left md:grid-cols-3">
|
||||||
size="lg"
|
{betaFit.map((item) => (
|
||||||
variant="outline"
|
<li
|
||||||
className="text-primary-button w-fit border-white/30 bg-white hover:bg-white/90"
|
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"
|
||||||
>
|
>
|
||||||
<a href={BETA_INVITE_MAILTO}>Start the conversation</a>
|
<span className="bg-primary-light mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full">
|
||||||
</Button>
|
<Check className="text-primary size-3" aria-hidden="true" />
|
||||||
</CardHeader>
|
</span>
|
||||||
</Card>
|
{item}
|
||||||
</section>
|
</li>
|
||||||
</div>
|
))}
|
||||||
</div>
|
</ul>
|
||||||
|
<div className="mt-10 flex flex-col items-center justify-center gap-3 sm:flex-row">
|
||||||
|
<Button asChild size="lg">
|
||||||
|
<a href={BETA_INVITE_MAILTO}>
|
||||||
|
Draft beta request
|
||||||
|
<ArrowRight className="size-4" aria-hidden="true" />
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
<Button asChild size="lg" variant="outline">
|
||||||
|
<Link href="/beta">What to expect</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<p className="text-muted-foreground mx-auto mt-6 max-w-xl text-xs leading-relaxed">
|
||||||
|
Start with your platform and whether you are testing as a patient,
|
||||||
|
caregiver, or both. Please do not email health details.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,388 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import {
|
||||||
|
AlertTriangle,
|
||||||
|
ArrowRight,
|
||||||
|
Check,
|
||||||
|
Database,
|
||||||
|
FileCheck2,
|
||||||
|
KeyRound,
|
||||||
|
LockKeyhole,
|
||||||
|
Wifi,
|
||||||
|
} from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import { SectionLabel } from "~/components/section-label";
|
||||||
|
import { Badge } from "~/components/ui/badge";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
import { productClaims, productRelease } from "~/content/product";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Privacy & evidence",
|
||||||
|
description:
|
||||||
|
"A task-by-task view of Medscribe's local architecture, network behavior, caregiver sharing, evidence, and beta limitations.",
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataMap = [
|
||||||
|
{
|
||||||
|
task: "Visit transcription",
|
||||||
|
location: "Device",
|
||||||
|
network: "No cloud inference",
|
||||||
|
control: "Recording requires consent",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
task: "Summary generation",
|
||||||
|
location: "Device",
|
||||||
|
network: "No cloud inference",
|
||||||
|
control: "The person reviews output",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
task: "Medication-label OCR",
|
||||||
|
location: "Device",
|
||||||
|
network: "No cloud inference",
|
||||||
|
control: "Extracted fields remain editable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
task: "Drug facts and known pairs",
|
||||||
|
location: "Bundled on device",
|
||||||
|
network: "No runtime fact lookup",
|
||||||
|
control: "Sources and coverage are disclosed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
task: "Model installation or update",
|
||||||
|
location: "Downloaded to device",
|
||||||
|
network: "Network may be required",
|
||||||
|
control: "Approved URLs and file integrity checks",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
task: "Caregiver sharing",
|
||||||
|
location: "Encrypted device-to-device snapshot",
|
||||||
|
network: "The person chooses ciphertext transport",
|
||||||
|
control: "Verify out of band; import trust enforcement is incomplete",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
task: "Primary record storage",
|
||||||
|
location: "Local app database",
|
||||||
|
network: "No Medscribe cloud account",
|
||||||
|
control: "The unlocked device remains a trust boundary",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
task: "Website analytics",
|
||||||
|
location: "Marketing site only",
|
||||||
|
network: "Optional Umami script when configured",
|
||||||
|
control: "Disabled when no website ID is configured",
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const protects = [
|
||||||
|
"Runtime disclosure of visits or medications to a cloud AI provider",
|
||||||
|
"A Medscribe account database becoming the default copy of the record",
|
||||||
|
"A readable caregiver export being sent to someone who was not paired",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const doesNotProtect = [
|
||||||
|
"Information viewed on an unlocked or compromised phone",
|
||||||
|
"A snapshot that a verified recipient already imported",
|
||||||
|
"Someone intentionally copying or sharing visible information",
|
||||||
|
"Every possible drug interaction or model failure",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export default function PrivacyPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section className="px-4 pt-16 pb-18 sm:px-6 sm:pt-22">
|
||||||
|
<div className="mx-auto max-w-6xl">
|
||||||
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
|
<Badge variant="success">
|
||||||
|
Last verified {productRelease.verifiedOn}
|
||||||
|
</Badge>
|
||||||
|
<Badge variant="outline">Internal beta</Badge>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-foreground mt-6 max-w-4xl font-serif text-5xl leading-[1.02] font-semibold tracking-[-0.03em] sm:text-6xl">
|
||||||
|
Privacy should be specific enough to challenge.
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground mt-6 max-w-3xl text-lg leading-relaxed sm:text-xl">
|
||||||
|
“Local” is not a magic word. Here is where each task happens, when a
|
||||||
|
network can be involved, what the design protects, and what it does
|
||||||
|
not.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="border-border-soft bg-surface/80 border-y px-4 py-18 sm:px-6 lg:py-22">
|
||||||
|
<div className="mx-auto max-w-6xl">
|
||||||
|
<SectionLabel>Data map</SectionLabel>
|
||||||
|
<h2 className="text-foreground mt-4 font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||||
|
Task by task, not “everything” absolutes.
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="border-border-soft bg-canvas mt-10 overflow-x-auto rounded-2xl border shadow-[0_18px_55px_rgba(15,23,42,0.06)]">
|
||||||
|
<table className="w-full min-w-[780px] border-collapse text-left text-sm">
|
||||||
|
<caption className="sr-only">
|
||||||
|
Medscribe tasks, processing location, network behavior, and user
|
||||||
|
control
|
||||||
|
</caption>
|
||||||
|
<thead>
|
||||||
|
<tr className="border-border-soft bg-surface-soft border-b">
|
||||||
|
{[
|
||||||
|
"Task",
|
||||||
|
"Where it happens",
|
||||||
|
"Network behavior",
|
||||||
|
"User control",
|
||||||
|
].map((heading) => (
|
||||||
|
<th
|
||||||
|
key={heading}
|
||||||
|
scope="col"
|
||||||
|
className="text-muted-foreground px-5 py-4 text-xs font-bold tracking-[0.1em] uppercase"
|
||||||
|
>
|
||||||
|
{heading}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{dataMap.map((row) => (
|
||||||
|
<tr
|
||||||
|
key={row.task}
|
||||||
|
className="border-border-soft/80 border-b last:border-0"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
scope="row"
|
||||||
|
className="text-foreground px-5 py-4 font-semibold"
|
||||||
|
>
|
||||||
|
{row.task}
|
||||||
|
</th>
|
||||||
|
<td className="text-muted-foreground px-5 py-4">
|
||||||
|
{row.location}
|
||||||
|
</td>
|
||||||
|
<td className="text-muted-foreground px-5 py-4">
|
||||||
|
{row.network}
|
||||||
|
</td>
|
||||||
|
<td className="text-muted-foreground px-5 py-4">
|
||||||
|
{row.control}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="px-4 py-20 sm:px-6 lg:py-28">
|
||||||
|
<div className="mx-auto max-w-6xl">
|
||||||
|
<div className="grid gap-6 lg:grid-cols-3">
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
icon: LockKeyhole,
|
||||||
|
title: "Supported AI work stays local",
|
||||||
|
body: productClaims.localAi.publicCopy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Database,
|
||||||
|
title: "The primary record stays local",
|
||||||
|
body: `${productClaims.localRecord.publicCopy} The beta relies on the device and operating system's protections; it does not claim a separately encrypted app database.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Wifi,
|
||||||
|
title: "Downloads are the network exception",
|
||||||
|
body: "Installing or updating a model can require a network. Approved model files are checked against an expected SHA-256 digest before use.",
|
||||||
|
},
|
||||||
|
].map((item) => {
|
||||||
|
const Icon = item.icon;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
key={item.title}
|
||||||
|
className="border-border-soft bg-surface rounded-2xl border p-6"
|
||||||
|
>
|
||||||
|
<span className="bg-primary-light text-primary flex size-11 items-center justify-center rounded-xl">
|
||||||
|
<Icon className="size-5" aria-hidden="true" />
|
||||||
|
</span>
|
||||||
|
<h3 className="text-foreground mt-5 font-serif text-2xl font-semibold">
|
||||||
|
{item.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-muted-foreground mt-3 text-sm leading-relaxed">
|
||||||
|
{item.body}
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="border-border-soft border-y bg-[#f4f0e8]/75 px-4 py-20 sm:px-6 lg:py-24 dark:bg-[#151b25]">
|
||||||
|
<div className="mx-auto max-w-6xl">
|
||||||
|
<div className="grid gap-12 lg:grid-cols-2">
|
||||||
|
<div>
|
||||||
|
<div className="bg-primary-light text-primary flex size-12 items-center justify-center rounded-xl">
|
||||||
|
<KeyRound className="size-6" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
<SectionLabel className="mt-6">Caregiver sharing</SectionLabel>
|
||||||
|
<h2 className="text-foreground mt-4 font-serif text-4xl font-semibold tracking-tight">
|
||||||
|
Verify a person, then export deliberately.
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-5 leading-relaxed">
|
||||||
|
New snapshots use authenticated encryption targeted to the
|
||||||
|
recipient key. Ciphertext integrity and wrong-recipient
|
||||||
|
rejection are tested. The current import path does not yet
|
||||||
|
require the sender to be in the recipient's active trust
|
||||||
|
list, and recipient-key metadata is not authenticated and
|
||||||
|
checked on import. Verify the sender out of band. The beta does
|
||||||
|
not claim the Signal protocol or live synchronization.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="border-border-soft bg-surface rounded-2xl border p-6 sm:p-8">
|
||||||
|
<p className="text-foreground font-semibold">
|
||||||
|
The practical rule
|
||||||
|
</p>
|
||||||
|
<ol className="mt-5 space-y-4">
|
||||||
|
{[
|
||||||
|
"Compare the safety number with the intended caregiver.",
|
||||||
|
"Review the snapshot before every material export.",
|
||||||
|
"Re-export after important changes; it is not automatic.",
|
||||||
|
"Treat revocation as a block on future local exports; it cannot invalidate already-exported ciphertext.",
|
||||||
|
].map((item, index) => (
|
||||||
|
<li
|
||||||
|
key={item}
|
||||||
|
className="text-muted-foreground flex gap-3 text-sm leading-relaxed"
|
||||||
|
>
|
||||||
|
<span className="bg-primary-light text-primary flex size-6 shrink-0 items-center justify-center rounded-full text-xs font-bold">
|
||||||
|
{index + 1}
|
||||||
|
</span>
|
||||||
|
{item}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="px-4 py-20 sm:px-6 lg:py-28">
|
||||||
|
<div className="mx-auto max-w-6xl">
|
||||||
|
<SectionLabel>Evidence, not superlatives</SectionLabel>
|
||||||
|
<div className="mt-4 grid gap-10 lg:grid-cols-[0.85fr_1.15fr] lg:items-end">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-foreground font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||||
|
What the current release can actually point to.
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<p className="text-muted-foreground leading-relaxed">
|
||||||
|
Medication language is based on named public reference datasets,
|
||||||
|
but condensed wording is not FDA-reviewed. Coverage counts
|
||||||
|
describe the bundle; they do not prove clinical completeness.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<dl className="mt-12 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
|
{[
|
||||||
|
[
|
||||||
|
productRelease.drugFacts.toLocaleString(),
|
||||||
|
"Condensed drug records",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
productRelease.rxNormTerms.toLocaleString(),
|
||||||
|
"RxNorm search terms",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
productRelease.interactionPairs.toLocaleString(),
|
||||||
|
"Known high-priority pairs",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
productRelease.flaggedForReview.toLocaleString(),
|
||||||
|
"Records flagged for review",
|
||||||
|
],
|
||||||
|
].map(([value, label]) => (
|
||||||
|
<div
|
||||||
|
key={label}
|
||||||
|
className="border-border-soft bg-surface rounded-2xl border p-6"
|
||||||
|
>
|
||||||
|
<dt className="text-muted-foreground text-sm leading-relaxed">
|
||||||
|
{label}
|
||||||
|
</dt>
|
||||||
|
<dd className="text-foreground mt-2 font-serif text-4xl font-semibold">
|
||||||
|
{value}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<div className="border-border-soft bg-surface-soft mt-6 flex gap-3 rounded-2xl border p-5">
|
||||||
|
<FileCheck2
|
||||||
|
className="text-primary mt-0.5 size-5 shrink-0"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<p className="text-muted-foreground text-sm leading-relaxed">
|
||||||
|
Source families include openFDA label data, RxNorm terminology,
|
||||||
|
MED-RT and DailyMed class inputs, plus a bounded high-priority
|
||||||
|
interaction list. Release dates and full methodology should be
|
||||||
|
published with each wider release.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="border-border-soft bg-surface/75 border-t px-4 py-20 sm:px-6">
|
||||||
|
<div className="mx-auto grid max-w-6xl gap-8 lg:grid-cols-2">
|
||||||
|
<div className="border-success/25 bg-success-light/45 rounded-2xl border p-6 sm:p-8">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Check className="text-success size-6" aria-hidden="true" />
|
||||||
|
<h2 className="text-foreground font-serif text-2xl font-semibold">
|
||||||
|
Designed to protect against
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<ul className="text-muted-foreground mt-5 space-y-3 text-sm leading-relaxed">
|
||||||
|
{protects.map((item) => (
|
||||||
|
<li key={item} className="border-success/30 border-l-2 pl-4">
|
||||||
|
{item}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-2xl border border-amber-500/25 bg-amber-50 p-6 text-slate-900 sm:p-8 dark:bg-amber-950/30 dark:text-slate-100">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<AlertTriangle
|
||||||
|
className="size-6 text-amber-700 dark:text-amber-300"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<h2 className="font-serif text-2xl font-semibold">
|
||||||
|
It does not eliminate
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<ul className="mt-5 space-y-3 text-sm leading-relaxed text-slate-600 dark:text-slate-300">
|
||||||
|
{doesNotProtect.map((item) => (
|
||||||
|
<li key={item} className="border-l-2 border-amber-500/35 pl-4">
|
||||||
|
{item}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-auto mt-10 flex max-w-6xl flex-col items-start justify-between gap-5 rounded-2xl bg-[#0f4f4b] p-7 text-white sm:flex-row sm:items-center sm:p-9">
|
||||||
|
<div>
|
||||||
|
<h2 className="font-serif text-3xl font-semibold">
|
||||||
|
Help test the boundary.
|
||||||
|
</h2>
|
||||||
|
<p className="mt-2 text-sm text-teal-50/75">
|
||||||
|
The internal beta is focused on correctness, comprehension, and
|
||||||
|
caregiver handoffs.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
asChild
|
||||||
|
size="lg"
|
||||||
|
variant="outline"
|
||||||
|
className="border-white/20 bg-white text-[#0f4f4b] hover:bg-teal-50"
|
||||||
|
>
|
||||||
|
<Link href="/beta">
|
||||||
|
Beta expectations
|
||||||
|
<ArrowRight className="size-4" aria-hidden="true" />
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
|
import { PrintButton } from "~/components/print-button";
|
||||||
|
import { SectionLabel } from "~/components/section-label";
|
||||||
|
import { WalgreensPrintSummary } from "~/components/walgreens-print-summary";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Walgreens executive summary",
|
||||||
|
robots: { index: false, follow: false, nocache: true },
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function WalgreensSummaryPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WalgreensPrintSummary />
|
||||||
|
<div className="print-hidden mx-auto max-w-2xl px-4 py-16 text-center sm:px-6 lg:py-24">
|
||||||
|
<SectionLabel>Executive summary · Walgreens</SectionLabel>
|
||||||
|
<h1 className="text-foreground mt-4 font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||||
|
The pharmacy executive summary
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground mx-auto mt-5 max-w-xl leading-relaxed">
|
||||||
|
This page is formatted as a two-page print document: what Medscribe
|
||||||
|
can do, and how every capability pays off twice, for the patient and
|
||||||
|
for Walgreens. Use Print PDF (or Cmd+P, then Save as PDF) to export
|
||||||
|
it.
|
||||||
|
</p>
|
||||||
|
<div className="mt-8 flex justify-center">
|
||||||
|
<PrintButton size="lg" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -17,11 +17,12 @@ const playfair = Playfair_Display({
|
|||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: {
|
title: {
|
||||||
default: "Medscribe | Be known when care gets complicated",
|
default: "Medscribe | Carry the context when care gets complicated",
|
||||||
template: "Medscribe | %s",
|
template: "Medscribe | %s",
|
||||||
},
|
},
|
||||||
description:
|
description:
|
||||||
"A private, emergency-ready medical context app for families managing complex care. Keep visits, medications, baseline, allergies, and caregiver handoffs current on device.",
|
"A private, patient-controlled beta that helps keep visits, medications, and critical context together on your device.",
|
||||||
|
applicationName: "Medscribe",
|
||||||
icons: {
|
icons: {
|
||||||
icon: [
|
icon: [
|
||||||
{ url: "/favicon.ico", sizes: "any" },
|
{ url: "/favicon.ico", sizes: "any" },
|
||||||
@@ -29,12 +30,14 @@ export const metadata: Metadata = {
|
|||||||
{ url: "/favicon-32x32.png", sizes: "32x32", type: "image/png" },
|
{ url: "/favicon-32x32.png", sizes: "32x32", type: "image/png" },
|
||||||
{ url: "/favicon-16x16.png", sizes: "16x16", type: "image/png" },
|
{ url: "/favicon-16x16.png", sizes: "16x16", type: "image/png" },
|
||||||
],
|
],
|
||||||
apple: [{ url: "/apple-touch-icon.png", sizes: "180x180", type: "image/png" }],
|
apple: [
|
||||||
|
{ url: "/apple-touch-icon.png", sizes: "180x180", type: "image/png" },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: "Medscribe",
|
title: "Medscribe",
|
||||||
description:
|
description:
|
||||||
"Be known when care gets complicated. Private medical context for families managing complex care.",
|
"Carry the context when care gets complicated. Private, patient-controlled medical context on your device.",
|
||||||
type: "website",
|
type: "website",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -43,7 +46,10 @@ export default function RootLayout({
|
|||||||
children,
|
children,
|
||||||
}: Readonly<{ children: React.ReactNode }>) {
|
}: Readonly<{ children: React.ReactNode }>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en" className={`${instrumentSans.variable} ${playfair.variable}`}>
|
<html
|
||||||
|
lang="en"
|
||||||
|
className={`${instrumentSans.variable} ${playfair.variable}`}
|
||||||
|
>
|
||||||
<body className="min-h-screen">
|
<body className="min-h-screen">
|
||||||
{children}
|
{children}
|
||||||
<UmamiAnalytics />
|
<UmamiAnalytics />
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import type { MetadataRoute } from "next";
|
||||||
|
|
||||||
|
export default function robots(): MetadataRoute.Robots {
|
||||||
|
return {
|
||||||
|
rules: {
|
||||||
|
userAgent: "*",
|
||||||
|
allow: "/",
|
||||||
|
disallow: [
|
||||||
|
"/api/",
|
||||||
|
"/briefs/",
|
||||||
|
"/cvs",
|
||||||
|
"/executive-summary",
|
||||||
|
"/landscape",
|
||||||
|
"/login",
|
||||||
|
"/walgreens",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
import Image from "next/image";
|
||||||
|
import {
|
||||||
|
HeartPulse,
|
||||||
|
Layers,
|
||||||
|
Mic,
|
||||||
|
MessageSquare,
|
||||||
|
ScanLine,
|
||||||
|
ShieldCheck,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pharmacy-specific print summary (CVS-targeted, reusable for other pharmacies).
|
||||||
|
* Two designed pages, built for the product rather than mirroring the Northwell
|
||||||
|
* layout:
|
||||||
|
* Page 1 - what Medscribe can do (capabilities, patient-facing power).
|
||||||
|
* Page 2 - every capability pays off twice (patient value -> CVS value).
|
||||||
|
* Reuses the `northwell-print-*` sheet/label classes from globals.css only as
|
||||||
|
* the page frame; the interior is its own design.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function PrintPage({
|
||||||
|
page,
|
||||||
|
children,
|
||||||
|
}: Readonly<{ page: number; children: React.ReactNode }>) {
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
className={
|
||||||
|
page === 1
|
||||||
|
? "northwell-print-page"
|
||||||
|
: "northwell-print-page northwell-print-page--continuation"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img className="northwell-print-wash" src="/print-wash.svg" alt="" />
|
||||||
|
{page === 1 ? (
|
||||||
|
<header className="northwell-print-header">
|
||||||
|
<div className="flex items-center gap-2.5">
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img className="h-6 w-auto" src="/medscribe-ms-logo.svg" alt="" />
|
||||||
|
<span className="font-serif text-[17px] font-semibold text-slate-950">
|
||||||
|
Medscribe
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-[9px] font-semibold tracking-[0.18em] text-slate-500 uppercase">
|
||||||
|
Executive summary · CVS Health
|
||||||
|
</span>
|
||||||
|
</header>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div className="northwell-print-content">{children}</div>
|
||||||
|
|
||||||
|
<footer className="northwell-print-footer">
|
||||||
|
<span>Page {page} of 2</span>
|
||||||
|
</footer>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const capabilities = [
|
||||||
|
{
|
||||||
|
icon: Mic,
|
||||||
|
title: "Understand a visit",
|
||||||
|
body: "Type, paste, snap the after-visit summary, or record the conversation with consent. Medscribe transcribes and summarizes it on the phone.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: ScanLine,
|
||||||
|
title: "Read a pill bottle",
|
||||||
|
body: "Point the camera at a prescription label and the drug, dose, and schedule land in the record automatically.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: MessageSquare,
|
||||||
|
title: "Answer the hard questions",
|
||||||
|
body: "Ask whether two medications are safe together and get an answer grounded in bundled FDA drug facts and known interaction pairs, not a chatbot's guess.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Layers,
|
||||||
|
title: "Hold the whole picture",
|
||||||
|
body: "Medications, allergies, conditions, baseline, and visit history across every provider, in one place the patient owns.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: HeartPulse,
|
||||||
|
title: "Be ready for an emergency",
|
||||||
|
body: "A patient-controlled Know Me card puts the time-critical facts one tap away for a caregiver or an ER.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: ShieldCheck,
|
||||||
|
title: "Share without exposure",
|
||||||
|
body: "End-to-end encrypted sharing lets a caregiver help, without a server ever reading the record.",
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const payoffs = [
|
||||||
|
{
|
||||||
|
patientTitle: "One picture, everywhere they are treated",
|
||||||
|
patient:
|
||||||
|
"Nothing falls through the cracks between a specialist, an urgent care, and the pharmacy.",
|
||||||
|
cvsTitle: "One central pharmacy",
|
||||||
|
cvs: "Refills route back to CVS and scattered scripts consolidate, instead of leaking to Amazon, Cost Plus, or mail order. Share of scripts, won.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
patientTitle: "Medications that fit their day",
|
||||||
|
patient:
|
||||||
|
"Reminders and refill nudges that actually help, so they stay on their medications.",
|
||||||
|
cvsTitle: "Star Ratings",
|
||||||
|
cvs: "Diabetes, hypertension, and statin adherence are triple-weighted in Part D Star Ratings: Quality Bonus Payments and retention for Aetna and SilverScript.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
patientTitle: "Fewer dangerous surprises",
|
||||||
|
patient:
|
||||||
|
"Interactions and duplicate prescriptions caught across every prescriber, not one pharmacy's slice.",
|
||||||
|
cvsTitle: "Lower cost of care",
|
||||||
|
cvs: "Fewer adverse events and ER visits: direct medical spend saved for the members shared with Caremark and Aetna.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
patientTitle: "Ready at the counter",
|
||||||
|
patient:
|
||||||
|
"They arrive with a clean list and their routine questions already answered.",
|
||||||
|
cvsTitle: "Pharmacist leverage",
|
||||||
|
cvs: "Less low-value counter volume, more time for the billable MTM and CMR work pharmacists already do.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
patientTitle: "A tool that is theirs",
|
||||||
|
patient:
|
||||||
|
"An advocate they control, on their own device. Not a sales pitch, and never on a server.",
|
||||||
|
cvsTitle: "Engagement without the liability",
|
||||||
|
cvs: "A CVS-branded habit patients actually open, with no PHI on a server: no new breach surface and no BAA to negotiate.",
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export function CvsPrintSummary() {
|
||||||
|
return (
|
||||||
|
<div className="northwell-print-only" aria-hidden="true">
|
||||||
|
<PrintPage page={1}>
|
||||||
|
<div className="grid grid-cols-[1.16fr_0.84fr] items-start gap-9 pt-5">
|
||||||
|
<div>
|
||||||
|
<p className="northwell-print-label">What Medscribe is</p>
|
||||||
|
<h1 className="mt-4 font-serif text-[40px] leading-[1.0] font-semibold tracking-[-0.035em] text-slate-950">
|
||||||
|
It captures, understands, and carries the patient's whole
|
||||||
|
medical picture.
|
||||||
|
</h1>
|
||||||
|
<p className="mt-5 text-[15px] leading-[1.55] text-slate-600">
|
||||||
|
Real medical AI on the patient's phone. Medscribe summarizes
|
||||||
|
visits, reads pill bottles, answers medication questions from
|
||||||
|
bundled drug facts, and keeps one emergency-ready record across
|
||||||
|
every provider. Nothing is sent to the cloud.
|
||||||
|
</p>
|
||||||
|
<div className="mt-6 grid grid-cols-2 gap-x-5 gap-y-2 text-[8.5px] font-bold tracking-[0.08em] text-slate-600 uppercase">
|
||||||
|
{[
|
||||||
|
"On-device AI",
|
||||||
|
"Facts-first",
|
||||||
|
"Patient-owned",
|
||||||
|
"iOS and Android beta",
|
||||||
|
].map((badge) => (
|
||||||
|
<span key={badge} className="flex items-center gap-2">
|
||||||
|
<span className="size-1 rounded-full bg-teal-600" />
|
||||||
|
{badge}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-auto w-[186px] rounded-[28px] border-[6px] border-slate-950 bg-slate-950 p-1 shadow-[0_18px_45px_rgba(15,23,42,0.18)]">
|
||||||
|
<div className="overflow-hidden rounded-[19px] bg-white">
|
||||||
|
<Image
|
||||||
|
src="/screenshots/medications.png"
|
||||||
|
alt="Medscribe medications screen"
|
||||||
|
width={1206}
|
||||||
|
height={2622}
|
||||||
|
priority
|
||||||
|
unoptimized
|
||||||
|
className="block h-auto w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 border-t border-teal-700/25 pt-5">
|
||||||
|
<p className="northwell-print-label">What it can do</p>
|
||||||
|
<h2 className="mt-2 font-serif text-[25px] leading-[1.12] font-semibold text-slate-950">
|
||||||
|
Real medical AI, working entirely on the phone
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 grid grid-cols-3 gap-x-7 gap-y-7">
|
||||||
|
{capabilities.map(({ icon: Icon, title, body }) => (
|
||||||
|
<div key={title}>
|
||||||
|
<Icon className="size-5 text-teal-700" strokeWidth={1.75} />
|
||||||
|
<h3 className="mt-2.5 font-serif text-[16px] leading-[1.15] font-semibold text-slate-950">
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
<p className="mt-1.5 text-[10px] leading-[1.5] text-slate-600">
|
||||||
|
{body}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 grid grid-cols-3 gap-7 border-t border-teal-700/25 pt-5">
|
||||||
|
{[
|
||||||
|
[
|
||||||
|
"On the device, not the cloud",
|
||||||
|
"Summarization and medication Q&A run on the phone. No health data goes to a server for AI, so there is no new breach surface and no BAA.",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Facts first, not guesswork",
|
||||||
|
"Drug facts, classes, and interaction pairs come from bundled reference data. The model helps phrase the answer; it does not invent it.",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Owned by the patient",
|
||||||
|
"One record that spans every provider, that the patient controls, carries, and decides how to share.",
|
||||||
|
],
|
||||||
|
].map(([title, body]) => (
|
||||||
|
<div key={title}>
|
||||||
|
<p className="font-serif text-[14px] font-semibold text-slate-950">
|
||||||
|
{title}
|
||||||
|
</p>
|
||||||
|
<p className="mt-1.5 text-[9.5px] leading-[1.5] text-slate-600">
|
||||||
|
{body}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</PrintPage>
|
||||||
|
|
||||||
|
<PrintPage page={2}>
|
||||||
|
<section className="pt-3">
|
||||||
|
<p className="northwell-print-label">Who it helps</p>
|
||||||
|
<h2 className="northwell-print-title">Every capability pays off twice</h2>
|
||||||
|
<p className="northwell-print-lede max-w-[660px]">
|
||||||
|
The features that make Medscribe genuinely useful to the patient are
|
||||||
|
the same ones that move the numbers CVS is measured on. Cross-provider
|
||||||
|
by design; one central pharmacy in practice.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className="mt-6 grid grid-cols-[0.92fr_1.08fr] gap-8">
|
||||||
|
<p className="text-[9px] font-bold tracking-[0.18em] text-slate-500 uppercase">
|
||||||
|
For the patient
|
||||||
|
</p>
|
||||||
|
<p className="text-[9px] font-bold tracking-[0.18em] text-teal-700 uppercase">
|
||||||
|
For CVS
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-1">
|
||||||
|
{payoffs.map((row) => (
|
||||||
|
<div
|
||||||
|
key={row.cvsTitle}
|
||||||
|
className="grid grid-cols-[0.92fr_1.08fr] gap-8 border-t border-slate-200 py-3"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-serif text-[14px] leading-[1.2] font-semibold text-slate-950">
|
||||||
|
{row.patientTitle}
|
||||||
|
</h3>
|
||||||
|
<p className="mt-1 text-[9.5px] leading-[1.45] text-slate-600">
|
||||||
|
{row.patient}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="border-l border-teal-600/30 pl-8">
|
||||||
|
<h3 className="font-serif text-[14px] leading-[1.2] font-semibold text-teal-900">
|
||||||
|
{row.cvsTitle}
|
||||||
|
</h3>
|
||||||
|
<p className="mt-1 text-[9.5px] leading-[1.45] text-slate-600">
|
||||||
|
{row.cvs}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="mt-5 bg-teal-50/70 px-5 py-3 text-[10px] leading-[1.5] text-slate-700">
|
||||||
|
<span className="font-semibold text-slate-900">
|
||||||
|
It sits beside the CVS stack, not on top of it.{" "}
|
||||||
|
</span>
|
||||||
|
Dispensing, Caremark benefits, and the CVS app keep doing their jobs.
|
||||||
|
Medscribe is the patient-owned layer that feeds value back in with
|
||||||
|
consent. A partnership unlocks co-branding, refill routing, and
|
||||||
|
adherence reporting against Star and EQuIPP measures.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<section className="mt-6 grid grid-cols-[1.12fr_0.88fr] items-start gap-9 border-t border-slate-300 pt-5">
|
||||||
|
<div>
|
||||||
|
<p className="northwell-print-label">The ask</p>
|
||||||
|
<h3 className="mt-3 font-serif text-[20px] leading-[1.22] font-semibold text-slate-950">
|
||||||
|
A short conversation about a record that moves adherence and keeps
|
||||||
|
refills at CVS.
|
||||||
|
</h3>
|
||||||
|
<p className="mt-2 text-[10.5px] leading-[1.5] text-slate-600">
|
||||||
|
Live in beta on iOS and Android today. The core patient experience
|
||||||
|
ships now; the business layer is what a partnership builds together.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="northwell-print-label">Who is behind it</p>
|
||||||
|
<h3 className="mt-3 font-serif text-[19px] font-semibold text-slate-950">
|
||||||
|
Sean O'Connor
|
||||||
|
</h3>
|
||||||
|
<p className="mt-3 text-[10.5px] leading-[1.5] text-slate-600">
|
||||||
|
A Bucknell computer science and engineering graduate who built
|
||||||
|
Medscribe after watching his family lose the full picture of his
|
||||||
|
grandfather's care.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</PrintPage>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,120 +1,122 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import type { KeyboardEvent } from "react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import {
|
import {
|
||||||
CalendarDays,
|
CalendarDays,
|
||||||
Camera,
|
|
||||||
Check,
|
Check,
|
||||||
ContactRound,
|
ContactRound,
|
||||||
Home,
|
|
||||||
MessageCircle,
|
MessageCircle,
|
||||||
Shield,
|
Pill,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
import { PhoneMockup } from "~/components/phone-mockup";
|
import { PhoneMockup } from "~/components/phone-mockup";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
const features = [
|
const features = [
|
||||||
{
|
|
||||||
id: "home",
|
|
||||||
label: "Profile",
|
|
||||||
icon: Home,
|
|
||||||
title: "One profile for the person you care for",
|
|
||||||
description:
|
|
||||||
"Visits, medications, reminders, and questions live together under the person they belong to.",
|
|
||||||
points: [
|
|
||||||
"Switch between everyone you care for",
|
|
||||||
"One home for the whole picture",
|
|
||||||
"Nothing scattered across apps",
|
|
||||||
],
|
|
||||||
image: "/screenshots/home.png",
|
|
||||||
alt: "Medscribe home dashboard with record visit, search, medications, and ask shortcuts",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "context",
|
|
||||||
label: "Context",
|
|
||||||
icon: ContactRound,
|
|
||||||
title: "The context behind the handoff",
|
|
||||||
description:
|
|
||||||
"Conditions, medication changes, visit summaries, and caregiver notes stay close enough to become useful when time is short.",
|
|
||||||
points: [
|
|
||||||
"Baseline, allergies, and conditions up front",
|
|
||||||
"Time-critical medications flagged",
|
|
||||||
"A source trail behind each fact",
|
|
||||||
],
|
|
||||||
image: "/screenshots/emergency-record.png",
|
|
||||||
alt: "Medscribe emergency record with critical care context",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "visits",
|
id: "visits",
|
||||||
label: "Visits",
|
label: "Visits",
|
||||||
icon: CalendarDays,
|
icon: CalendarDays,
|
||||||
title: "Visit recordings that refresh the record",
|
title: "Turn a visit into context you can review",
|
||||||
description:
|
description:
|
||||||
"A visit can become searchable context, so a medication change or follow-up instruction does not depend on memory alone.",
|
"Type or paste notes, import a portal screenshot or document, or use consent-gated recording. The beta creates a local summary for the person to check.",
|
||||||
points: [
|
points: [
|
||||||
"Consent-gated, on-device recording",
|
"Supported transcription and summarization run on device",
|
||||||
"Searchable transcript and summary",
|
"The person reviews generated information",
|
||||||
"Follow-ups captured, not forgotten",
|
"Structured visit-to-record change review is still roadmap",
|
||||||
],
|
],
|
||||||
image: "/screenshots/visit-timeline.png",
|
image: "/screenshots/visit-detail.png",
|
||||||
alt: "Medscribe visit timeline with appointment cards",
|
alt: "Synthetic Medscribe beta visit detail showing a visit summary and follow-up information",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "meds",
|
id: "medications",
|
||||||
label: "Meds",
|
label: "Medications",
|
||||||
icon: Camera,
|
icon: Pill,
|
||||||
title: "Medication OCR, reminders, and history",
|
title: "Keep the daily medication picture close",
|
||||||
description:
|
description:
|
||||||
"Prescription labels, reminders, refill alerts, and dose history help the family see what is supposed to happen and what actually happened.",
|
"Scan a label, correct the extracted fields, set reminders, follow refills, and see a local intake history without asking a language model to invent drug facts.",
|
||||||
points: [
|
points: [
|
||||||
"Scan labels with the camera",
|
"Label text remains editable before it becomes the record",
|
||||||
"Reminders and refill alerts",
|
"Bundled facts and known pairs use named reference datasets",
|
||||||
"Dose history at a glance",
|
"Coverage is intentionally bounded, not a complete interaction check",
|
||||||
],
|
],
|
||||||
image: "/screenshots/medications.png",
|
image: "/screenshots/medication-detail.png",
|
||||||
alt: "Medscribe medications screen with active medications",
|
alt: "Synthetic Medscribe beta medication detail with schedule and medication information",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "ask",
|
id: "questions",
|
||||||
label: "Ask",
|
label: "Questions",
|
||||||
icon: MessageCircle,
|
icon: MessageCircle,
|
||||||
title: "Medication questions grounded in the record",
|
title: "Facts first, fluent answers second",
|
||||||
description:
|
description:
|
||||||
"Plain-language questions can draw from the person's record and bundled FDA facts, while keeping diagnosis and dosing with clinicians.",
|
"Medication questions can use the person’s local record and bundled fields. Dosing changes, diagnosis, and treatment decisions stay with a clinician or pharmacist.",
|
||||||
points: [
|
points: [
|
||||||
"Plain-language answers",
|
"Known facts render from bundled data",
|
||||||
"Grounded in the record and FDA facts",
|
"The local model helps with conversational phrasing",
|
||||||
"Defers dosing and diagnosis to clinicians",
|
"High-risk or unsupported questions defer to a professional",
|
||||||
],
|
],
|
||||||
image: "/screenshots/chat.png",
|
image: "/screenshots/chat-response.png",
|
||||||
alt: "Medscribe Ask chat answering a question about blood pressure medications",
|
alt: "Synthetic Medscribe beta answer with medication context and a safety boundary",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "privacy",
|
id: "handoff",
|
||||||
label: "Privacy",
|
label: "Know Me",
|
||||||
icon: Shield,
|
icon: ContactRound,
|
||||||
title: "Private by architecture, not policy",
|
title: "Prepare one readable handoff",
|
||||||
description:
|
description:
|
||||||
"The sensitive work happens on device, so the family record is not another cloud inbox waiting to be mined.",
|
"The Know Me view brings selected medications, allergies, conditions, baseline, and contacts together. Current, unconfirmed, and review-needed states make uncertainty visible.",
|
||||||
points: [
|
points: [
|
||||||
"On-device AI, no cloud processing",
|
"Medication schedules can include source and confirmation",
|
||||||
"No inbox to breach or mine",
|
"The person can show or share the snapshot they reviewed",
|
||||||
"You control what is ever shared",
|
"It complements—not replaces—clinical verification",
|
||||||
],
|
],
|
||||||
image: "/screenshots/settings.png",
|
image: "/screenshots/emergency-record.png",
|
||||||
alt: "Medscribe settings showing on-device AI models and privacy notice",
|
alt: "Synthetic Medscribe beta Know Me handoff with active medications and review state",
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
type FeatureId = (typeof features)[number]["id"];
|
||||||
|
|
||||||
export function FeatureShowcase() {
|
export function FeatureShowcase() {
|
||||||
const [activeId, setActiveId] = useState<(typeof features)[number]["id"]>("home");
|
const [activeId, setActiveId] = useState<FeatureId>("visits");
|
||||||
const active = features.find((f) => f.id === activeId) ?? features[0];
|
const active =
|
||||||
|
features.find((feature) => feature.id === activeId) ?? features[0];
|
||||||
|
|
||||||
|
const onTabKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {
|
||||||
|
const activeIndex = features.findIndex(
|
||||||
|
(feature) => feature.id === activeId,
|
||||||
|
);
|
||||||
|
let nextIndex: number | undefined;
|
||||||
|
|
||||||
|
if (event.key === "ArrowRight" || event.key === "ArrowDown") {
|
||||||
|
nextIndex = (activeIndex + 1) % features.length;
|
||||||
|
} else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
|
||||||
|
nextIndex = (activeIndex - 1 + features.length) % features.length;
|
||||||
|
} else if (event.key === "Home") {
|
||||||
|
nextIndex = 0;
|
||||||
|
} else if (event.key === "End") {
|
||||||
|
nextIndex = features.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextIndex === undefined) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
const nextFeature = features[nextIndex];
|
||||||
|
if (!nextFeature) return;
|
||||||
|
|
||||||
|
setActiveId(nextFeature.id);
|
||||||
|
document.getElementById(`feature-tab-${nextFeature.id}`)?.focus();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid items-center gap-8 lg:grid-cols-[minmax(0,1fr)_300px] lg:gap-12">
|
<div className="grid items-center gap-10 lg:grid-cols-[minmax(0,1fr)_340px] lg:gap-16">
|
||||||
<div className="flex flex-col gap-5">
|
<div className="flex min-w-0 flex-col gap-6">
|
||||||
<div className="flex flex-wrap gap-2">
|
<div
|
||||||
|
role="tablist"
|
||||||
|
aria-label="Explore the Medscribe beta"
|
||||||
|
className="flex max-w-full gap-2 overflow-x-auto pb-1"
|
||||||
|
>
|
||||||
{features.map((feature) => {
|
{features.map((feature) => {
|
||||||
const Icon = feature.icon;
|
const Icon = feature.icon;
|
||||||
const isActive = feature.id === activeId;
|
const isActive = feature.id === activeId;
|
||||||
@@ -122,49 +124,67 @@ export function FeatureShowcase() {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={feature.id}
|
key={feature.id}
|
||||||
|
id={`feature-tab-${feature.id}`}
|
||||||
type="button"
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-selected={isActive}
|
||||||
|
aria-controls={`feature-panel-${feature.id}`}
|
||||||
|
tabIndex={isActive ? 0 : -1}
|
||||||
onClick={() => setActiveId(feature.id)}
|
onClick={() => setActiveId(feature.id)}
|
||||||
|
onKeyDown={onTabKeyDown}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex items-center gap-2 rounded-full border px-3 py-1.5 text-sm font-medium transition-all",
|
"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",
|
||||||
isActive
|
isActive
|
||||||
? "border-primary-button bg-primary-button text-white shadow-sm"
|
? "border-primary-button bg-primary-button text-white shadow-sm"
|
||||||
: "border-border-soft bg-surface text-muted-foreground hover:border-primary/20 hover:text-foreground",
|
: "border-border-soft bg-surface text-muted-foreground hover:border-primary/30 hover:text-foreground",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Icon className="size-4" />
|
<Icon className="size-4" aria-hidden="true" />
|
||||||
{feature.label}
|
{feature.label}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div
|
||||||
<h3 className="font-serif text-2xl font-semibold tracking-tight text-foreground sm:text-3xl">
|
id={`feature-panel-${active.id}`}
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby={`feature-tab-${active.id}`}
|
||||||
|
tabIndex={0}
|
||||||
|
className="focus-visible:ring-primary/40 rounded-xl outline-none focus-visible:ring-2"
|
||||||
|
>
|
||||||
|
<p className="text-primary text-xs font-bold tracking-[0.14em] uppercase">
|
||||||
|
Current beta
|
||||||
|
</p>
|
||||||
|
<h3 className="text-foreground mt-3 max-w-xl font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
||||||
{active.title}
|
{active.title}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="max-w-xl text-sm leading-relaxed text-muted-foreground sm:text-base">
|
<p className="text-muted-foreground mt-4 max-w-xl leading-relaxed">
|
||||||
{active.description}
|
{active.description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul className="grid gap-x-5 gap-y-2.5 sm:grid-cols-2">
|
<ul className="mt-6 grid gap-3 sm:grid-cols-2">
|
||||||
{active.points.map((point) => (
|
{active.points.map((point) => (
|
||||||
<li
|
<li
|
||||||
key={point}
|
key={point}
|
||||||
className="flex items-start gap-2 text-sm leading-snug text-foreground"
|
className="text-foreground flex items-start gap-2.5 text-sm leading-relaxed"
|
||||||
>
|
>
|
||||||
<span className="mt-0.5 flex size-4 shrink-0 items-center justify-center rounded-full bg-primary-light">
|
<span className="bg-primary-light mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full">
|
||||||
<Check className="size-3 text-primary" />
|
<Check className="text-primary size-3" aria-hidden="true" />
|
||||||
</span>
|
</span>
|
||||||
<span>{point}</span>
|
<span>{point}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="justify-self-center lg:justify-self-end">
|
<div className="justify-self-center lg:justify-self-end">
|
||||||
<div className="flex items-center justify-center rounded-2xl border border-border-soft bg-gradient-to-b from-surface-soft to-primary-light/30 p-6 shadow-[0_18px_45px_rgba(15,23,42,0.10)]">
|
<div className="border-border-soft bg-surface-soft/80 rounded-[2.5rem] border p-5 shadow-[0_30px_80px_rgba(15,23,42,0.10)] sm:p-7">
|
||||||
<PhoneMockup src={active.image} alt={active.alt} width={188} />
|
<PhoneMockup src={active.image} alt={active.alt} width={220} />
|
||||||
|
<p className="text-muted-foreground mt-4 text-center text-xs">
|
||||||
|
Synthetic demo data · beta interface
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,381 @@
|
|||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
function PrintPage({
|
||||||
|
page,
|
||||||
|
children,
|
||||||
|
}: Readonly<{ page: number; children: React.ReactNode }>) {
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
className={
|
||||||
|
page === 1
|
||||||
|
? "northwell-print-page"
|
||||||
|
: "northwell-print-page northwell-print-page--continuation"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img className="northwell-print-wash" src="/print-wash.svg" alt="" />
|
||||||
|
{page === 1 ? (
|
||||||
|
<header className="northwell-print-header">
|
||||||
|
<div className="flex items-center gap-2.5">
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img className="h-6 w-auto" src="/medscribe-ms-logo.svg" alt="" />
|
||||||
|
<span className="font-serif text-[17px] font-semibold text-slate-950">
|
||||||
|
Medscribe
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-[9px] font-semibold tracking-[0.18em] text-slate-500 uppercase">
|
||||||
|
Executive summary · Northwell Health
|
||||||
|
</span>
|
||||||
|
</header>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div className="northwell-print-content">{children}</div>
|
||||||
|
|
||||||
|
<footer className="northwell-print-footer">
|
||||||
|
<span>Page {page} of 3</span>
|
||||||
|
</footer>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const people = [
|
||||||
|
[
|
||||||
|
"The student on their own",
|
||||||
|
"Managing prescriptions and appointments for the first time, away from the parent who used to track it all.",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"The newly diagnosed",
|
||||||
|
"A new routine, new instructions, and follow-ups that are a blur by the next visit.",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"The long-distance caregiver",
|
||||||
|
"An adult child coordinating a parent's medications with siblings who cover different days.",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"The emergency",
|
||||||
|
"An older patient at an out-of-network ER, where no one has met them or knows which medications cannot be delayed.",
|
||||||
|
],
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const productPillars = [
|
||||||
|
[
|
||||||
|
"Capture visits",
|
||||||
|
"Type or paste notes, import a portal screenshot, attach a document, or use consent-gated recording. Summarization stays on device.",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Keep medications current",
|
||||||
|
"Pill-bottle label recognition, reminders, refill tracking, dose history, and questions grounded in bundled FDA facts.",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Be ready to hand off",
|
||||||
|
"A patient-controlled Know Me record and encrypted caregiver sharing keep critical context ready when the chart is incomplete.",
|
||||||
|
],
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const northwellFit = [
|
||||||
|
[
|
||||||
|
"AI patients can trust",
|
||||||
|
"Visit summarization and medication Q&A run on the phone, while medication facts come from bundled reference data rather than model recall. It is a clear AI posture for patients, privacy teams, and clinicians.",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"A Northwell story worth telling",
|
||||||
|
"Northwell can lead with a concrete patient benefit: one private, emergency-ready picture that travels across systems. The experience could carry Northwell's brand without pretending to replace MyChart.",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"An everyday touchstone",
|
||||||
|
"Reminders, refills, dose tracking, questions, and record updates create useful reasons to return between appointments. Each moment can point the patient back to the Northwell care team.",
|
||||||
|
],
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const recordContents = [
|
||||||
|
"Active medications, doses, schedules, and refill status",
|
||||||
|
"Allergies, conditions, baseline, and the context that changes a handoff",
|
||||||
|
"Visit summaries, follow-up instructions, and questions for the next visit",
|
||||||
|
"Emergency contacts and a concise, patient-controlled Know Me record",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export function NorthwellPrintSummary() {
|
||||||
|
return (
|
||||||
|
<div className="northwell-print-only" aria-hidden="true">
|
||||||
|
<PrintPage page={1}>
|
||||||
|
<div className="grid grid-cols-[1.16fr_0.84fr] items-start gap-9 pt-5">
|
||||||
|
<div>
|
||||||
|
<p className="northwell-print-label">The patient-owned record</p>
|
||||||
|
<h1 className="mt-4 font-serif text-[44px] leading-[0.98] font-semibold tracking-[-0.035em] text-slate-950">
|
||||||
|
Be known when care gets complicated
|
||||||
|
</h1>
|
||||||
|
<p className="mt-5 text-[16px] leading-[1.55] text-slate-600">
|
||||||
|
Medscribe keeps a patient's medications, visits, baseline,
|
||||||
|
and critical care context current between appointments, then makes
|
||||||
|
that picture ready for a caregiver, a new doctor, or an ER.
|
||||||
|
</p>
|
||||||
|
<div className="mt-6 grid grid-cols-2 gap-x-5 gap-y-2 text-[8.5px] font-bold tracking-[0.08em] text-slate-600 uppercase">
|
||||||
|
{[
|
||||||
|
"On-device AI",
|
||||||
|
"Emergency-ready",
|
||||||
|
"Patient-owned",
|
||||||
|
"iOS and Android beta",
|
||||||
|
].map((badge) => (
|
||||||
|
<span key={badge} className="flex items-center gap-2">
|
||||||
|
<span className="size-1 rounded-full bg-teal-600" />
|
||||||
|
{badge}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-auto w-[194px] rounded-[28px] border-[6px] border-slate-950 bg-slate-950 p-1 shadow-[0_18px_45px_rgba(15,23,42,0.18)]">
|
||||||
|
<div className="overflow-hidden rounded-[19px] bg-white">
|
||||||
|
<Image
|
||||||
|
src="/screenshots/home.png"
|
||||||
|
alt="Medscribe home screen"
|
||||||
|
width={1206}
|
||||||
|
height={2622}
|
||||||
|
priority
|
||||||
|
unoptimized
|
||||||
|
className="block h-auto w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-7 border-t border-teal-700/25 pt-5">
|
||||||
|
<p className="northwell-print-label">The job</p>
|
||||||
|
<p className="mt-3 font-serif text-[25px] leading-[1.2] font-semibold text-slate-950">
|
||||||
|
One trustworthy picture that survives fragmented care, missed
|
||||||
|
appointments, secondhand updates, and emergency handoffs.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 grid grid-cols-3 gap-5">
|
||||||
|
{[
|
||||||
|
[
|
||||||
|
"Keep it current",
|
||||||
|
"Everyday visit and medication workflows maintain the record.",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Make it obvious",
|
||||||
|
"Time-critical medications and baseline context rise to the top.",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Keep it controlled",
|
||||||
|
"The patient decides what is shared, when, and with whom.",
|
||||||
|
],
|
||||||
|
].map(([title, body]) => (
|
||||||
|
<div key={title} className="pr-3">
|
||||||
|
<p className="font-serif text-[16px] font-semibold text-slate-950">
|
||||||
|
{title}
|
||||||
|
</p>
|
||||||
|
<p className="mt-2 text-[10.5px] leading-[1.5] text-slate-600">
|
||||||
|
{body}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 grid grid-cols-[0.72fr_1.28fr] gap-5">
|
||||||
|
<div>
|
||||||
|
<p className="northwell-print-label">One living picture</p>
|
||||||
|
<p className="mt-2 text-[10.5px] leading-[1.5] text-slate-600">
|
||||||
|
The record is useful in an emergency because ordinary use keeps it
|
||||||
|
from becoming a stale export.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ul className="grid grid-cols-2 gap-x-5 gap-y-2 text-[9.5px] leading-[1.45] text-slate-600">
|
||||||
|
{recordContents.map((item) => (
|
||||||
|
<li
|
||||||
|
key={item}
|
||||||
|
className="relative pl-3 before:absolute before:top-[0.42em] before:left-0 before:size-1 before:rounded-full before:bg-teal-500 before:content-['']"
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</PrintPage>
|
||||||
|
|
||||||
|
<PrintPage page={2}>
|
||||||
|
<section className="pt-3">
|
||||||
|
<p className="northwell-print-label">What it is</p>
|
||||||
|
<h2 className="northwell-print-title">
|
||||||
|
A medical record the patient owns and carries
|
||||||
|
</h2>
|
||||||
|
<p className="northwell-print-lede">
|
||||||
|
The daily tools are not separate features. They are the maintenance
|
||||||
|
system for one emergency-ready record.
|
||||||
|
</p>
|
||||||
|
<div className="mt-5 grid grid-cols-3 gap-5">
|
||||||
|
{productPillars.map(([title, body], index) => (
|
||||||
|
<div key={title} className="min-h-[120px] pr-3">
|
||||||
|
<span className="text-[10px] font-bold text-teal-700">
|
||||||
|
0{index + 1}
|
||||||
|
</span>
|
||||||
|
<h3 className="mt-2 font-serif text-[17px] font-semibold text-slate-950">
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
<p className="mt-2 text-[10.5px] leading-[1.5] text-slate-600">
|
||||||
|
{body}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mt-7">
|
||||||
|
<p className="northwell-print-label">Who it is for</p>
|
||||||
|
<h2 className="northwell-print-title">
|
||||||
|
Anyone holding care together
|
||||||
|
</h2>
|
||||||
|
<div className="mt-4 grid grid-cols-2 gap-x-7 gap-y-2">
|
||||||
|
{people.map(([title, body]) => (
|
||||||
|
<div key={title} className="min-h-[70px] pr-4">
|
||||||
|
<h3 className="font-serif text-[15px] font-semibold text-slate-950">
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
<p className="mt-1.5 text-[10.5px] leading-[1.45] text-slate-600">
|
||||||
|
{body}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mt-7 border-l-4 border-teal-600 bg-teal-50/50 py-5 pr-5 pl-6">
|
||||||
|
<p className="northwell-print-label">What makes it different</p>
|
||||||
|
<h2 className="mt-3 font-serif text-[24px] font-semibold text-slate-950">
|
||||||
|
Every alternative owns a slice. Medscribe owns the whole job.
|
||||||
|
</h2>
|
||||||
|
<div className="mt-3 grid grid-cols-2 gap-6 text-[10.5px] leading-[1.55] text-slate-600">
|
||||||
|
<p>
|
||||||
|
Every institutional record stops at its own walls. Epic aggregates
|
||||||
|
Epic; urgent care, community specialists, and facilities on other
|
||||||
|
systems can still sit outside the picture in front of a clinician.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Visit notetakers, medication trackers, record vaults, and
|
||||||
|
emergency IDs each solve one slice. Medscribe connects daily
|
||||||
|
maintenance to the handoff across Epic and non-Epic care, on the
|
||||||
|
patient's device and under the patient's control.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mt-5 grid grid-cols-2 gap-7 border-t border-slate-300 pt-5">
|
||||||
|
<div>
|
||||||
|
<p className="northwell-print-label">Facts first</p>
|
||||||
|
<p className="mt-2 text-[10px] leading-[1.5] text-slate-600">
|
||||||
|
Medication facts, drug classes, and known interaction pairs come
|
||||||
|
from bundled reference data. The language model helps with
|
||||||
|
phrasing; it is not treated as the source of medical facts.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="northwell-print-label">Local by default</p>
|
||||||
|
<p className="mt-2 text-[10px] leading-[1.5] text-slate-600">
|
||||||
|
Visit summarization and medication Q&A run on the phone.
|
||||||
|
Caregiver sharing is end-to-end encrypted, so a server is not
|
||||||
|
asked to read the patient's record in order to coordinate
|
||||||
|
care.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</PrintPage>
|
||||||
|
|
||||||
|
<PrintPage page={3}>
|
||||||
|
<section className="pt-3">
|
||||||
|
<p className="northwell-print-label">The Northwell opportunity</p>
|
||||||
|
<h2 className="northwell-print-title">
|
||||||
|
A trusted AI touchstone between visits
|
||||||
|
</h2>
|
||||||
|
<p className="northwell-print-lede max-w-[650px]">
|
||||||
|
Medscribe gives Northwell a useful presence in the part of care no
|
||||||
|
portal owns: the ordinary days when patients remember, refill,
|
||||||
|
question, coordinate, and try to follow the plan.
|
||||||
|
</p>
|
||||||
|
<div className="mt-6 grid grid-cols-3 gap-7">
|
||||||
|
{northwellFit.map(([title, body], index) => (
|
||||||
|
<div key={title}>
|
||||||
|
<span className="text-[10px] font-bold text-teal-700">
|
||||||
|
0{index + 1}
|
||||||
|
</span>
|
||||||
|
<h3 className="font-serif text-[16px] font-semibold text-slate-950">
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
<p className="mt-2 text-[10px] leading-[1.55] text-slate-600">
|
||||||
|
{body}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mt-7 bg-teal-50/60 px-6 py-5">
|
||||||
|
<p className="northwell-print-label">The touchstone</p>
|
||||||
|
<h2 className="mt-2 font-serif text-[24px] leading-[1.18] font-semibold text-slate-950">
|
||||||
|
MyChart organizes the appointment. Medscribe helps the patient live
|
||||||
|
the plan.
|
||||||
|
</h2>
|
||||||
|
<p className="mt-3 text-[11px] leading-[1.55] text-slate-600">
|
||||||
|
A reminder, a refill, a quick question, or an update to the record
|
||||||
|
becomes a small moment of continuity. Presented as part of the
|
||||||
|
Northwell experience, those moments can reinforce the care plan and
|
||||||
|
point the patient back to the team that knows them.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mt-6 border-t border-slate-300 pt-5">
|
||||||
|
<p className="northwell-print-label">How it fits the stack</p>
|
||||||
|
<div className="mt-4 grid grid-cols-2 gap-9">
|
||||||
|
<div>
|
||||||
|
<h3 className="font-serif text-[16px] font-semibold text-slate-950">
|
||||||
|
Epic remains the record of care
|
||||||
|
</h3>
|
||||||
|
<p className="mt-2 text-[10.5px] leading-[1.55] text-slate-600">
|
||||||
|
Northwell's clinical workflows, orders, results, messages,
|
||||||
|
and institutional history stay where they belong. Medscribe is
|
||||||
|
not another EHR and does not need to displace MyChart.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-serif text-[16px] font-semibold text-slate-950">
|
||||||
|
The patient carries the missing context
|
||||||
|
</h3>
|
||||||
|
<p className="mt-2 text-[10.5px] leading-[1.55] text-slate-600">
|
||||||
|
Non-Epic visits, medication changes, caregiver knowledge, and
|
||||||
|
emergency context stay with the patient. Consent governs when
|
||||||
|
that picture is shared back into a Northwell interaction.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="mt-6 grid grid-cols-[1.12fr_0.88fr] gap-9 border-t border-slate-300 pt-5">
|
||||||
|
<div>
|
||||||
|
<p className="northwell-print-label">The marketing value</p>
|
||||||
|
<h3 className="mt-3 font-serif text-[19px] leading-[1.25] font-semibold text-slate-950">
|
||||||
|
A patient promise people can understand
|
||||||
|
</h3>
|
||||||
|
<p className="mt-2 text-[10.5px] leading-[1.5] text-slate-600">
|
||||||
|
Private AI. One current picture. Ready for the caregiver, the new
|
||||||
|
doctor, or the ER. That is a more human story than “AI in
|
||||||
|
healthcare,” and it gives Northwell a visible way to stand
|
||||||
|
for continuity beyond its own walls.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="northwell-print-label">Built and live</p>
|
||||||
|
<h3 className="mt-3 font-serif text-[19px] font-semibold text-slate-950">
|
||||||
|
Sean O'Connor
|
||||||
|
</h3>
|
||||||
|
<p className="mt-3 text-[10.5px] leading-[1.5] text-slate-600">
|
||||||
|
A Bucknell computer science and engineering graduate who built
|
||||||
|
Medscribe after watching his family lose the full picture of his
|
||||||
|
grandfather's care. The product is live in beta on iOS and
|
||||||
|
Android today.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</PrintPage>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { IPhoneMockup } from "react-device-mockup";
|
|
||||||
|
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
@@ -27,33 +24,26 @@ export function PhoneMockup({
|
|||||||
const isSimulatorCapture = captureMode === "simulator";
|
const isSimulatorCapture = captureMode === "simulator";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("mx-auto w-fit drop-shadow-[0_18px_45px_rgba(15,23,42,0.14)]", className)}>
|
<div
|
||||||
<IPhoneMockup
|
className={cn(
|
||||||
screenWidth={width}
|
"mx-auto overflow-hidden rounded-[2.25rem] border-[7px] border-slate-950 bg-slate-950 p-1 shadow-[0_24px_60px_rgba(15,23,42,0.18)]",
|
||||||
screenType="island"
|
className,
|
||||||
frameColor="#111827"
|
)}
|
||||||
statusbarColor="#f8fafc"
|
style={{ width }}
|
||||||
hideStatusBar
|
>
|
||||||
hideNavBar
|
<div className="bg-surface relative aspect-[1206/2622] overflow-hidden rounded-[1.65rem]">
|
||||||
transparentNavBar
|
<Image
|
||||||
>
|
src={src}
|
||||||
<div className="relative h-full w-full overflow-hidden bg-surface">
|
alt={alt}
|
||||||
<Image
|
fill
|
||||||
src={src}
|
priority={priority}
|
||||||
alt={alt}
|
sizes={`${width}px`}
|
||||||
width={1206}
|
className={cn(
|
||||||
height={2622}
|
"object-cover",
|
||||||
priority={priority}
|
isSimulatorCapture ? "scale-[1.22] object-[50%_58%]" : "object-top",
|
||||||
unoptimized
|
)}
|
||||||
className={cn(
|
/>
|
||||||
"h-full w-full object-cover",
|
</div>
|
||||||
isSimulatorCapture
|
|
||||||
? "scale-[1.22] object-[50%_58%]"
|
|
||||||
: "object-top",
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</IPhoneMockup>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
|
type ProductDeviceProps = {
|
||||||
|
src: string;
|
||||||
|
alt: string;
|
||||||
|
className?: string;
|
||||||
|
sizes?: string;
|
||||||
|
priority?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A precomposited product capture using the licensed device bezels in
|
||||||
|
* `public/screenshots/pitch`. Keeping the frame in the source image preserves
|
||||||
|
* the bezel details without adding layout or client-side work.
|
||||||
|
*/
|
||||||
|
export function ProductDevice({
|
||||||
|
src,
|
||||||
|
alt,
|
||||||
|
className,
|
||||||
|
sizes = "(min-width: 1024px) 360px, (min-width: 640px) 320px, 72vw",
|
||||||
|
priority = false,
|
||||||
|
}: ProductDeviceProps) {
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
src={src}
|
||||||
|
alt={alt}
|
||||||
|
width={810}
|
||||||
|
height={1656}
|
||||||
|
sizes={sizes}
|
||||||
|
priority={priority}
|
||||||
|
className={cn(
|
||||||
|
"h-auto w-full drop-shadow-[0_35px_35px_rgba(15,23,42,0.22)] dark:drop-shadow-[0_38px_42px_rgba(0,0,0,0.5)]",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,6 +1,19 @@
|
|||||||
export function SectionLabel({ children }: { children: React.ReactNode }) {
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
|
export function SectionLabel({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<p className="text-xs font-semibold tracking-widest text-muted-foreground uppercase">
|
<p
|
||||||
|
className={cn(
|
||||||
|
"text-muted-foreground text-xs font-semibold tracking-widest uppercase",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,24 +1,69 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import { MedscribeLogo } from "~/components/medscribe-logo";
|
||||||
|
|
||||||
|
const footerLinks = [
|
||||||
|
{ href: "/how-it-works", label: "How it works" },
|
||||||
|
{ href: "/privacy", label: "Privacy & evidence" },
|
||||||
|
{ href: "/beta", label: "Beta access" },
|
||||||
|
] as const;
|
||||||
|
|
||||||
export function SiteFooter() {
|
export function SiteFooter() {
|
||||||
return (
|
return (
|
||||||
<footer className="print-hidden border-t border-border-soft bg-surface/70">
|
<footer className="print-hidden border-border-soft bg-surface/80 border-t">
|
||||||
<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 className="mx-auto grid max-w-6xl gap-10 px-4 py-12 text-sm sm:px-6 md:grid-cols-[1.3fr_0.7fr_0.7fr]">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-serif text-base font-semibold text-foreground">Medscribe</p>
|
<Link
|
||||||
<p className="mt-1 max-w-md leading-relaxed">
|
href="/"
|
||||||
Be known when care gets complicated.
|
className="text-foreground focus-visible:ring-primary/40 inline-flex items-center gap-2.5 rounded-md outline-none focus-visible:ring-2"
|
||||||
|
>
|
||||||
|
<MedscribeLogo className="h-7 w-auto" />
|
||||||
|
<span className="font-serif text-lg font-semibold">Medscribe</span>
|
||||||
|
</Link>
|
||||||
|
<p className="text-muted-foreground mt-4 max-w-md leading-relaxed">
|
||||||
|
A private, patient-controlled beta for keeping visits, medications,
|
||||||
|
and critical context together on the device you carry.
|
||||||
|
</p>
|
||||||
|
<p className="text-muted-foreground mt-3 text-xs leading-relaxed">
|
||||||
|
Medscribe does not diagnose, prescribe, or replace emergency
|
||||||
|
services or a clinician's verification.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:text-right">
|
|
||||||
<p>
|
<nav aria-label="Footer product navigation">
|
||||||
|
<p className="text-foreground font-semibold">Product</p>
|
||||||
|
<ul className="text-muted-foreground mt-3 space-y-2.5">
|
||||||
|
{footerLinks.map((item) => (
|
||||||
|
<li key={item.href}>
|
||||||
|
<Link
|
||||||
|
href={item.href}
|
||||||
|
className="hover:text-foreground focus-visible:ring-primary/40 rounded-sm underline-offset-4 outline-none hover:underline focus-visible:ring-2"
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-foreground font-semibold">Contact</p>
|
||||||
|
<p className="text-muted-foreground mt-3 leading-relaxed">
|
||||||
Built by{" "}
|
Built by{" "}
|
||||||
<a
|
<a
|
||||||
href="https://soconnor.dev"
|
href="https://soconnor.dev"
|
||||||
className="text-foreground underline-offset-4 hover:underline"
|
className="text-foreground focus-visible:ring-primary/40 rounded-sm underline-offset-4 outline-none hover:underline focus-visible:ring-2"
|
||||||
>
|
>
|
||||||
Sean O'Connor
|
Sean O'Connor
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-1 text-xs text-muted-foreground/80">
|
<a
|
||||||
|
href="mailto:sean@soconnor.dev?subject=Medscribe%20website%20question"
|
||||||
|
className="text-foreground focus-visible:ring-primary/40 mt-2 inline-block rounded-sm underline-offset-4 outline-none hover:underline focus-visible:ring-2"
|
||||||
|
>
|
||||||
|
sean@soconnor.dev
|
||||||
|
</a>
|
||||||
|
<p className="text-muted-foreground/80 mt-5 text-xs">
|
||||||
© {new Date().getFullYear()} Hadlock Holdings LLC
|
© {new Date().getFullYear()} Hadlock Holdings LLC
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,26 +1,73 @@
|
|||||||
|
import { Menu } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
import { MedscribeLogo } from "~/components/medscribe-logo";
|
import { MedscribeLogo } from "~/components/medscribe-logo";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
|
||||||
|
const navigation = [
|
||||||
|
{ href: "/how-it-works", label: "How it works" },
|
||||||
|
{ href: "/privacy", label: "Privacy & evidence" },
|
||||||
|
] as const;
|
||||||
|
|
||||||
export function SiteHeader() {
|
export function SiteHeader() {
|
||||||
return (
|
return (
|
||||||
<header className="print-hidden sticky top-3 z-50 px-3 sm:px-6">
|
<header className="print-hidden border-border-soft/70 bg-canvas/88 sticky top-0 z-50 border-b backdrop-blur-lg">
|
||||||
<div className="relative mx-auto max-w-5xl">
|
<div className="mx-auto flex h-16 max-w-6xl items-center gap-5 px-4 sm:px-6">
|
||||||
<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
|
||||||
<Link
|
href="/"
|
||||||
href="/"
|
className="text-foreground focus-visible:ring-primary/40 flex shrink-0 items-center gap-2.5 rounded-md transition-opacity outline-none hover:opacity-75 focus-visible:ring-2"
|
||||||
className="relative z-10 flex items-center text-foreground transition-opacity hover:opacity-80"
|
aria-label="Medscribe home"
|
||||||
aria-label="Medscribe home"
|
>
|
||||||
>
|
<MedscribeLogo className="h-7 w-auto" />
|
||||||
<MedscribeLogo className="h-7 w-auto" />
|
<span className="font-serif text-lg font-semibold tracking-tight">
|
||||||
</Link>
|
|
||||||
<p className="pointer-events-none absolute inset-x-0 text-center font-serif text-lg font-semibold tracking-tight text-foreground">
|
|
||||||
Medscribe
|
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>
|
</span>
|
||||||
</div>
|
</Link>
|
||||||
|
|
||||||
|
<nav
|
||||||
|
aria-label="Primary navigation"
|
||||||
|
className="ml-auto hidden items-center gap-1 md:flex"
|
||||||
|
>
|
||||||
|
{navigation.map((item) => (
|
||||||
|
<Link
|
||||||
|
key={item.href}
|
||||||
|
href={item.href}
|
||||||
|
className="text-muted-foreground hover:bg-surface-soft hover:text-foreground focus-visible:ring-primary/40 rounded-lg px-3 py-2 text-sm font-medium transition-colors outline-none focus-visible:ring-2"
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
<Button asChild size="sm" className="ml-2">
|
||||||
|
<Link href="/beta">Request beta access</Link>
|
||||||
|
</Button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<details className="group relative ml-auto md:hidden">
|
||||||
|
<summary className="border-border-soft bg-surface text-foreground focus-visible:ring-primary/40 flex size-11 cursor-pointer list-none items-center justify-center rounded-xl border outline-none marker:content-none focus-visible:ring-2 [&::-webkit-details-marker]:hidden">
|
||||||
|
<Menu className="size-5" aria-hidden="true" />
|
||||||
|
<span className="sr-only">Open navigation</span>
|
||||||
|
</summary>
|
||||||
|
<nav
|
||||||
|
aria-label="Mobile navigation"
|
||||||
|
className="border-border-soft bg-surface absolute top-13 right-0 flex w-64 flex-col rounded-2xl border p-2 shadow-[0_18px_50px_rgba(15,23,42,0.16)]"
|
||||||
|
>
|
||||||
|
{navigation.map((item) => (
|
||||||
|
<Link
|
||||||
|
key={item.href}
|
||||||
|
href={item.href}
|
||||||
|
className="text-foreground hover:bg-surface-soft focus-visible:ring-primary/40 rounded-xl px-4 py-3 text-sm font-medium outline-none focus-visible:ring-2"
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
<Link
|
||||||
|
href="/beta"
|
||||||
|
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 beta access
|
||||||
|
</Link>
|
||||||
|
</nav>
|
||||||
|
</details>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority";
|
|||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex shrink-0 items-center justify-center gap-2 rounded-[14px] 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-[14px] 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",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
|
|||||||
@@ -0,0 +1,308 @@
|
|||||||
|
import Image from "next/image";
|
||||||
|
import {
|
||||||
|
HeartPulse,
|
||||||
|
Layers,
|
||||||
|
Mic,
|
||||||
|
MessageSquare,
|
||||||
|
ScanLine,
|
||||||
|
ShieldCheck,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pharmacy-specific print summary (Walgreens variant). Page 1 (the product) is
|
||||||
|
* identical to the CVS piece; page 2's "For Walgreens" column reflects that
|
||||||
|
* Walgreens does not own a PBM or insurer the way CVS owns Caremark and Aetna,
|
||||||
|
* so adherence and cost value flow to pharmacy performance reimbursement and to
|
||||||
|
* Walgreens' plan and value-based-care partners, not an owned plan.
|
||||||
|
* Reuses the `northwell-print-*` sheet/label classes from globals.css as the
|
||||||
|
* page frame only.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function PrintPage({
|
||||||
|
page,
|
||||||
|
children,
|
||||||
|
}: Readonly<{ page: number; children: React.ReactNode }>) {
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
className={
|
||||||
|
page === 1
|
||||||
|
? "northwell-print-page"
|
||||||
|
: "northwell-print-page northwell-print-page--continuation"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img className="northwell-print-wash" src="/print-wash.svg" alt="" />
|
||||||
|
{page === 1 ? (
|
||||||
|
<header className="northwell-print-header">
|
||||||
|
<div className="flex items-center gap-2.5">
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img className="h-6 w-auto" src="/medscribe-ms-logo.svg" alt="" />
|
||||||
|
<span className="font-serif text-[17px] font-semibold text-slate-950">
|
||||||
|
Medscribe
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-[9px] font-semibold tracking-[0.18em] text-slate-500 uppercase">
|
||||||
|
Executive summary · Walgreens
|
||||||
|
</span>
|
||||||
|
</header>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div className="northwell-print-content">{children}</div>
|
||||||
|
|
||||||
|
<footer className="northwell-print-footer">
|
||||||
|
<span>Page {page} of 2</span>
|
||||||
|
</footer>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const capabilities = [
|
||||||
|
{
|
||||||
|
icon: Mic,
|
||||||
|
title: "Understand a visit",
|
||||||
|
body: "Type, paste, snap the after-visit summary, or record the conversation with consent. Medscribe transcribes and summarizes it on the phone.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: ScanLine,
|
||||||
|
title: "Read a pill bottle",
|
||||||
|
body: "Point the camera at a prescription label and the drug, dose, and schedule land in the record automatically.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: MessageSquare,
|
||||||
|
title: "Answer the hard questions",
|
||||||
|
body: "Ask whether two medications are safe together and get an answer grounded in bundled FDA drug facts and known interaction pairs, not a chatbot's guess.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Layers,
|
||||||
|
title: "Hold the whole picture",
|
||||||
|
body: "Medications, allergies, conditions, baseline, and visit history across every provider and pharmacy, in one place the patient owns.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: HeartPulse,
|
||||||
|
title: "Be ready for an emergency",
|
||||||
|
body: "A patient-controlled Know Me card puts the time-critical facts one tap away for a caregiver or an ER.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: ShieldCheck,
|
||||||
|
title: "Share without exposure",
|
||||||
|
body: "End-to-end encrypted sharing lets a caregiver help, without a server ever reading the record.",
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const payoffs = [
|
||||||
|
{
|
||||||
|
patientTitle: "One picture, everywhere they are treated",
|
||||||
|
patient:
|
||||||
|
"Nothing falls through the cracks between a specialist, an urgent care, and the pharmacy.",
|
||||||
|
bizTitle: "One central pharmacy",
|
||||||
|
biz: "Refills route back to Walgreens and scattered scripts consolidate, instead of leaking to Amazon, CVS, Cost Plus, or mail order. Share of scripts, won.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
patientTitle: "Medications that fit their day",
|
||||||
|
patient:
|
||||||
|
"Reminders and refill nudges that actually help, so they stay on their medications.",
|
||||||
|
bizTitle: "Pharmacy quality and Star Ratings",
|
||||||
|
biz: "The diabetes, hypertension, and statin adherence measures are triple-weighted in Part D Star Ratings, which drives performance reimbursement (EQuIPP, performance-based networks) and quality for the plans Walgreens serves.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
patientTitle: "Fewer dangerous surprises",
|
||||||
|
patient:
|
||||||
|
"Interactions and duplicate prescriptions caught across every prescriber, not one pharmacy's slice.",
|
||||||
|
bizTitle: "Lower cost of care",
|
||||||
|
biz: "Fewer adverse events and ER visits: medical spend saved for the plan and value-based-care partners Walgreens shares members with.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
patientTitle: "Ready at the counter",
|
||||||
|
patient:
|
||||||
|
"They arrive with a clean list and their routine questions already answered.",
|
||||||
|
bizTitle: "Pharmacist leverage",
|
||||||
|
biz: "Less low-value counter volume, more time for the billable MTM and CMR work pharmacists already do.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
patientTitle: "A tool that is theirs",
|
||||||
|
patient:
|
||||||
|
"An advocate they control, on their own device. Not a sales pitch, and never on a server.",
|
||||||
|
bizTitle: "Engagement without the liability",
|
||||||
|
biz: "A Walgreens-branded habit patients actually open through myWalgreens, with no PHI on a server: no new breach surface and no BAA to negotiate.",
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export function WalgreensPrintSummary() {
|
||||||
|
return (
|
||||||
|
<div className="northwell-print-only" aria-hidden="true">
|
||||||
|
<PrintPage page={1}>
|
||||||
|
<div className="grid grid-cols-[1.16fr_0.84fr] items-start gap-9 pt-5">
|
||||||
|
<div>
|
||||||
|
<p className="northwell-print-label">What Medscribe is</p>
|
||||||
|
<h1 className="mt-4 font-serif text-[40px] leading-[1.0] font-semibold tracking-[-0.035em] text-slate-950">
|
||||||
|
It captures, understands, and carries the patient's whole
|
||||||
|
medical picture.
|
||||||
|
</h1>
|
||||||
|
<p className="mt-5 text-[15px] leading-[1.55] text-slate-600">
|
||||||
|
Real medical AI on the patient's phone. Medscribe summarizes
|
||||||
|
visits, reads pill bottles, answers medication questions from
|
||||||
|
bundled drug facts, and keeps one emergency-ready record across
|
||||||
|
every doctor and pharmacy. Nothing is sent to the cloud.
|
||||||
|
</p>
|
||||||
|
<div className="mt-6 grid grid-cols-2 gap-x-5 gap-y-2 text-[8.5px] font-bold tracking-[0.08em] text-slate-600 uppercase">
|
||||||
|
{[
|
||||||
|
"On-device AI",
|
||||||
|
"Facts-first",
|
||||||
|
"Patient-owned",
|
||||||
|
"iOS and Android beta",
|
||||||
|
].map((badge) => (
|
||||||
|
<span key={badge} className="flex items-center gap-2">
|
||||||
|
<span className="size-1 rounded-full bg-teal-600" />
|
||||||
|
{badge}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-auto w-[186px] rounded-[28px] border-[6px] border-slate-950 bg-slate-950 p-1 shadow-[0_18px_45px_rgba(15,23,42,0.18)]">
|
||||||
|
<div className="overflow-hidden rounded-[19px] bg-white">
|
||||||
|
<Image
|
||||||
|
src="/screenshots/medications.png"
|
||||||
|
alt="Medscribe medications screen"
|
||||||
|
width={1206}
|
||||||
|
height={2622}
|
||||||
|
priority
|
||||||
|
unoptimized
|
||||||
|
className="block h-auto w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 border-t border-teal-700/25 pt-5">
|
||||||
|
<p className="northwell-print-label">What it can do</p>
|
||||||
|
<h2 className="mt-2 font-serif text-[25px] leading-[1.12] font-semibold text-slate-950">
|
||||||
|
Real medical AI, working entirely on the phone
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 grid grid-cols-3 gap-x-7 gap-y-7">
|
||||||
|
{capabilities.map(({ icon: Icon, title, body }) => (
|
||||||
|
<div key={title}>
|
||||||
|
<Icon className="size-5 text-teal-700" strokeWidth={1.75} />
|
||||||
|
<h3 className="mt-2.5 font-serif text-[16px] leading-[1.15] font-semibold text-slate-950">
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
<p className="mt-1.5 text-[10px] leading-[1.5] text-slate-600">
|
||||||
|
{body}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 grid grid-cols-3 gap-7 border-t border-teal-700/25 pt-5">
|
||||||
|
{[
|
||||||
|
[
|
||||||
|
"On the device, not the cloud",
|
||||||
|
"Summarization and medication Q&A run on the phone. No health data goes to a server for AI, so there is no new breach surface and no BAA.",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Facts first, not guesswork",
|
||||||
|
"Drug facts, classes, and interaction pairs come from bundled reference data. The model helps phrase the answer; it does not invent it.",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Owned by the patient",
|
||||||
|
"One record that spans every provider and pharmacy, that the patient controls, carries, and decides how to share.",
|
||||||
|
],
|
||||||
|
].map(([title, body]) => (
|
||||||
|
<div key={title}>
|
||||||
|
<p className="font-serif text-[14px] font-semibold text-slate-950">
|
||||||
|
{title}
|
||||||
|
</p>
|
||||||
|
<p className="mt-1.5 text-[9.5px] leading-[1.5] text-slate-600">
|
||||||
|
{body}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</PrintPage>
|
||||||
|
|
||||||
|
<PrintPage page={2}>
|
||||||
|
<section className="pt-3">
|
||||||
|
<p className="northwell-print-label">Who it helps</p>
|
||||||
|
<h2 className="northwell-print-title">Every capability pays off twice</h2>
|
||||||
|
<p className="northwell-print-lede max-w-[660px]">
|
||||||
|
The features that make Medscribe genuinely useful to the patient are
|
||||||
|
the same ones that move the numbers Walgreens is measured on.
|
||||||
|
Cross-provider by design; one central pharmacy in practice.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className="mt-6 grid grid-cols-[0.92fr_1.08fr] gap-8">
|
||||||
|
<p className="text-[9px] font-bold tracking-[0.18em] text-slate-500 uppercase">
|
||||||
|
For the patient
|
||||||
|
</p>
|
||||||
|
<p className="text-[9px] font-bold tracking-[0.18em] text-teal-700 uppercase">
|
||||||
|
For Walgreens
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-1">
|
||||||
|
{payoffs.map((row) => (
|
||||||
|
<div
|
||||||
|
key={row.bizTitle}
|
||||||
|
className="grid grid-cols-[0.92fr_1.08fr] gap-8 border-t border-slate-200 py-3"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-serif text-[14px] leading-[1.2] font-semibold text-slate-950">
|
||||||
|
{row.patientTitle}
|
||||||
|
</h3>
|
||||||
|
<p className="mt-1 text-[9.5px] leading-[1.45] text-slate-600">
|
||||||
|
{row.patient}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="border-l border-teal-600/30 pl-8">
|
||||||
|
<h3 className="font-serif text-[14px] leading-[1.2] font-semibold text-teal-900">
|
||||||
|
{row.bizTitle}
|
||||||
|
</h3>
|
||||||
|
<p className="mt-1 text-[9.5px] leading-[1.45] text-slate-600">
|
||||||
|
{row.biz}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="mt-5 bg-teal-50/70 px-5 py-3 text-[10px] leading-[1.5] text-slate-700">
|
||||||
|
<span className="font-semibold text-slate-900">
|
||||||
|
It sits beside the Walgreens stack, not on top of it.{" "}
|
||||||
|
</span>
|
||||||
|
Dispensing, myWalgreens, and the Walgreens app keep doing their jobs.
|
||||||
|
Medscribe is the patient-owned layer that feeds value back in with
|
||||||
|
consent. A partnership unlocks co-branding, refill routing, and
|
||||||
|
adherence reporting against Star and EQuIPP measures.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<section className="mt-6 grid grid-cols-[1.12fr_0.88fr] items-start gap-9 border-t border-slate-300 pt-5">
|
||||||
|
<div>
|
||||||
|
<p className="northwell-print-label">The ask</p>
|
||||||
|
<h3 className="mt-3 font-serif text-[20px] leading-[1.22] font-semibold text-slate-950">
|
||||||
|
A short conversation about a record that moves adherence and keeps
|
||||||
|
refills at Walgreens.
|
||||||
|
</h3>
|
||||||
|
<p className="mt-2 text-[10.5px] leading-[1.5] text-slate-600">
|
||||||
|
Live in beta on iOS and Android today. The core patient experience
|
||||||
|
ships now; the business layer is what a partnership builds together.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="northwell-print-label">Who is behind it</p>
|
||||||
|
<h3 className="mt-3 font-serif text-[19px] font-semibold text-slate-950">
|
||||||
|
Sean O'Connor
|
||||||
|
</h3>
|
||||||
|
<p className="mt-3 text-[10.5px] leading-[1.5] text-slate-600">
|
||||||
|
A Bucknell computer science and engineering graduate who built
|
||||||
|
Medscribe after watching his family lose the full picture of his
|
||||||
|
grandfather's care.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</PrintPage>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
export type ClaimStatus = "built" | "beta-verified" | "roadmap";
|
||||||
|
|
||||||
|
export type ProductClaim = {
|
||||||
|
publicCopy: string;
|
||||||
|
status: ClaimStatus;
|
||||||
|
evidence: string;
|
||||||
|
verifiedOn: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const productRelease = {
|
||||||
|
stage: "Internal beta",
|
||||||
|
platforms: "iOS and Android",
|
||||||
|
verifiedOn: "August 20, 2026",
|
||||||
|
drugFacts: 203,
|
||||||
|
rxNormTerms: 33_185,
|
||||||
|
interactionPairs: 1_150,
|
||||||
|
flaggedForReview: 76,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const productClaims = {
|
||||||
|
localAi: {
|
||||||
|
publicCopy:
|
||||||
|
"Visit transcription and supported AI tasks run on the device, without cloud AI inference.",
|
||||||
|
status: "built",
|
||||||
|
evidence: "App architecture and release-path audit",
|
||||||
|
verifiedOn: "2026-08-20",
|
||||||
|
},
|
||||||
|
localRecord: {
|
||||||
|
publicCopy:
|
||||||
|
"The primary record is stored locally on the person's device, with no Medscribe cloud account.",
|
||||||
|
status: "built",
|
||||||
|
evidence: "Local SQLite data architecture",
|
||||||
|
verifiedOn: "2026-08-20",
|
||||||
|
},
|
||||||
|
medicationFacts: {
|
||||||
|
publicCopy:
|
||||||
|
"Medication facts and known interaction pairs are read from bundled reference data rather than model memory.",
|
||||||
|
status: "built",
|
||||||
|
evidence: "Bundled data release manifest and deterministic answer paths",
|
||||||
|
verifiedOn: "2026-08-20",
|
||||||
|
},
|
||||||
|
caregiverSharing: {
|
||||||
|
publicCopy:
|
||||||
|
"A person can manually export an encrypted snapshot targeted to a caregiver's public key.",
|
||||||
|
status: "built",
|
||||||
|
evidence:
|
||||||
|
"QR pairing, safety-number display, encrypted export, ciphertext integrity, and wrong-recipient rejection",
|
||||||
|
verifiedOn: "2026-08-20",
|
||||||
|
},
|
||||||
|
recordFreshness: {
|
||||||
|
publicCopy:
|
||||||
|
"Medication schedules can carry source, confirmation, timezone, and freshness state in the beta.",
|
||||||
|
status: "built",
|
||||||
|
evidence: "Medication schedule and Know Me freshness implementation",
|
||||||
|
verifiedOn: "2026-08-20",
|
||||||
|
},
|
||||||
|
broaderProvenance: {
|
||||||
|
publicCopy:
|
||||||
|
"Source and freshness for conditions, allergies, baseline, and visit-derived changes are planned next.",
|
||||||
|
status: "roadmap",
|
||||||
|
evidence: "Current product roadmap",
|
||||||
|
verifiedOn: "2026-08-20",
|
||||||
|
},
|
||||||
|
} satisfies Record<string, ProductClaim>;
|
||||||
|
|
||||||
|
export const publicRoutes = [
|
||||||
|
"/",
|
||||||
|
"/how-it-works",
|
||||||
|
"/privacy",
|
||||||
|
"/beta",
|
||||||
|
] as const;
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
/** Shared beta / early-access copy for the marketing site. */
|
/** Shared beta / early-access copy for the marketing site. */
|
||||||
|
|
||||||
export const BETA_SUMMARY =
|
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.";
|
"Medscribe is in internal beta on iOS and Android. Visit transcription and supported AI tasks run on the device, without cloud AI inference.";
|
||||||
|
|
||||||
export const BETA_CTA_TITLE = "Try Medscribe";
|
export const BETA_CTA_TITLE = "Request beta access";
|
||||||
|
|
||||||
export const BETA_CTA_BODY =
|
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.";
|
"The beta is for people managing repeated visits or several medications, and for family caregivers willing to review the record and report errors.";
|
||||||
|
|
||||||
export const BETA_INVITE_MAILTO =
|
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";
|
"mailto:sean@soconnor.dev?subject=Medscribe%20beta%20access&body=Hi%20Sean%2C%0D%0A%0D%0AI%27m%20interested%20in%20the%20Medscribe%20beta.%0D%0A%0D%0APlatform%20(iOS%20or%20Android)%3A%0D%0AI%27m%20testing%20as%20(patient%2C%20caregiver%2C%20or%20both)%3A%0D%0AI%20can%20test%20with%20a%20caregiver%20(yes%20or%20no)%3A%0D%0A%0D%0APlease%20do%20not%20include%20diagnoses%2C%20medication%20names%2C%20or%20other%20health%20details%20in%20this%20email.%0D%0A%0D%0AThanks%21";
|
||||||
|
|
||||||
export const BETA_FOOTER_NOTE =
|
export const BETA_FOOTER_NOTE =
|
||||||
"Beta on iOS (TestFlight) and Android (Google Play internal testing).";
|
"Beta on iOS (TestFlight) and Android (Google Play internal testing).";
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--font-sans: var(--font-instrument-sans), ui-sans-serif, system-ui, sans-serif;
|
--font-sans:
|
||||||
|
var(--font-instrument-sans), ui-sans-serif, system-ui, sans-serif;
|
||||||
--font-serif: var(--font-playfair), ui-serif, Georgia, serif;
|
--font-serif: var(--font-playfair), ui-serif, Georgia, serif;
|
||||||
|
|
||||||
/* Light palette: mirrors the app's `lightColors` (constants/colors.ts). */
|
/* Light palette: mirrors the app's `lightColors` (constants/colors.ts). */
|
||||||
@@ -53,7 +54,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-canvas font-sans text-foreground antialiased;
|
@apply bg-canvas text-foreground font-sans antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background: color-mix(in srgb, var(--color-primary) 24%, transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +93,31 @@
|
|||||||
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
html {
|
||||||
|
scroll-behavior: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-hover:hover {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Branded one-page PDF sheet: a green-to-white gradient washed over a faint
|
* Branded one-page PDF sheet: a green-to-white gradient washed over a faint
|
||||||
* teal grid. Only rendered for print (the on-screen page mirrors the homepage).
|
* teal grid. Only rendered for print (the on-screen page mirrors the homepage).
|
||||||
@@ -97,42 +127,147 @@
|
|||||||
linear-gradient(150deg, #ccfbf1 0%, #f0fdfa 30%, #ffffff 64%),
|
linear-gradient(150deg, #ccfbf1 0%, #f0fdfa 30%, #ffffff 64%),
|
||||||
linear-gradient(rgba(15, 118, 110, 0.07) 1px, transparent 1px),
|
linear-gradient(rgba(15, 118, 110, 0.07) 1px, transparent 1px),
|
||||||
linear-gradient(90deg, rgba(15, 118, 110, 0.06) 1px, transparent 1px);
|
linear-gradient(90deg, rgba(15, 118, 110, 0.06) 1px, transparent 1px);
|
||||||
background-size: auto, 22px 22px, 22px 22px;
|
background-size:
|
||||||
|
auto,
|
||||||
|
22px 22px,
|
||||||
|
22px 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.northwell-print-only {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@page {
|
||||||
|
size: letter;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
@page {
|
:root {
|
||||||
margin: 0.25in;
|
color-scheme: light !important;
|
||||||
size: letter;
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
print-color-adjust: exact !important;
|
||||||
|
-webkit-print-color-adjust: exact !important;
|
||||||
|
animation: none !important;
|
||||||
|
opacity: 1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
background: #ffffff !important;
|
width: 8.5in !important;
|
||||||
print-color-adjust: exact;
|
margin: 0 !important;
|
||||||
-webkit-print-color-adjust: exact;
|
padding: 0 !important;
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
color: #0f172a !important;
|
color: #0f172a !important;
|
||||||
|
background: #ffffff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.print-hidden {
|
.print-hidden {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.print-page {
|
.northwell-print-only {
|
||||||
max-width: none !important;
|
display: block !important;
|
||||||
padding: 0 !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.print-page * {
|
.northwell-print-page {
|
||||||
box-shadow: none !important;
|
position: relative;
|
||||||
animation: none !important;
|
box-sizing: border-box;
|
||||||
|
width: 8.5in;
|
||||||
|
height: 11in;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0.42in 0.55in 0.46in;
|
||||||
|
break-after: page;
|
||||||
|
page-break-after: always;
|
||||||
|
background: #f8fafc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.print-sheet {
|
.northwell-print-page:last-child {
|
||||||
print-color-adjust: exact;
|
break-after: auto;
|
||||||
-webkit-print-color-adjust: exact;
|
page-break-after: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.northwell-print-wash {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.northwell-print-header,
|
||||||
|
.northwell-print-content,
|
||||||
|
.northwell-print-footer {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.northwell-print-header {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.42in;
|
||||||
|
right: 0.55in;
|
||||||
|
left: 0.55in;
|
||||||
|
display: flex;
|
||||||
|
height: 0.43in;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-bottom: 1px solid #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.northwell-print-content {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.85in;
|
||||||
|
right: 0.55in;
|
||||||
|
left: 0.55in;
|
||||||
|
height: 9.43in;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.northwell-print-page--continuation .northwell-print-content {
|
||||||
|
top: 0.42in;
|
||||||
|
height: 9.86in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.northwell-print-footer {
|
||||||
|
position: absolute;
|
||||||
|
right: 0.55in;
|
||||||
|
bottom: 0.46in;
|
||||||
|
left: 0.55in;
|
||||||
|
display: flex;
|
||||||
|
height: 0.26in;
|
||||||
|
align-items: end;
|
||||||
|
justify-content: center;
|
||||||
|
border-top: 1px solid #cbd5e1;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 8px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.northwell-print-label {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.18em;
|
||||||
|
color: #0f766e;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.northwell-print-title {
|
||||||
|
margin-top: 0.12in;
|
||||||
|
font-family: var(--font-playfair), Georgia, serif;
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 1.08;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.northwell-print-lede {
|
||||||
|
margin-top: 0.12in;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #475569;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||