4 Commits
Author SHA1 Message Date
soconnor 81109f36ea feat: launch public product website 2026-08-20 20:13:35 -04:00
soconnor d3c30859b2 finish up marketing push for northwell 2026-07-13 18:36:20 -04:00
soconnorandCursor 2f8dcd66ad Refocus homepage as Northwell exec summary with float layout.
Single-page strategic pitch for health-system readers; phone mockup wraps in newspaper-style columns; retire multi-page nav and redirect /executive-summary to home.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-13 14:53:23 -04:00
soconnor a590a506f8 Sharpen exec summary for the Epic-transition reader; tighten flow
- Executive summary "For health systems" now leads with Epic explicitly and
  the three cards are framed as what the EHR structurally can't do, feeding
  context back into Epic, and patient-owned persistence — aimed at a reader
  whose world is the Epic transition.
- Merge the redundant "Where we fit" section into "Why this is different"
  (kept its one strong highlight card), dropping the now-unused Badge import.
- Homepage: add a one-line "works alongside the systems a care team already
  uses, not instead of them" bridge in the differentiation section, keeping
  the hero purely consumer.
2026-07-10 00:48:53 -04:00
41 changed files with 4381 additions and 623 deletions
+2 -1
View File
@@ -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
+35 -5
View File
@@ -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`.
+1059
View File
File diff suppressed because it is too large Load Diff
+51
View File
@@ -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=="],
} }
} }
+1
View File
@@ -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"
+14
View File
@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

+108
View File
@@ -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();
}
+66
View File
@@ -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();
}
+226
View File
@@ -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>
</>
);
}
+32
View File
@@ -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>
</>
);
}
+17 -46
View File
@@ -1,6 +1,7 @@
import type { CSSProperties } from "react"; import type { CSSProperties } from "react";
import type { Metadata } from "next"; import type { Metadata } from "next";
import { redirect } from "next/navigation";
import Image from "next/image"; import Image from "next/image";
import { MedscribeLogo } from "~/components/medscribe-logo"; import { MedscribeLogo } from "~/components/medscribe-logo";
@@ -8,7 +9,6 @@ import { PhoneMockup } from "~/components/phone-mockup";
import { PrintButton } from "~/components/print-button"; import { PrintButton } from "~/components/print-button";
import { SectionLabel } from "~/components/section-label"; import { SectionLabel } from "~/components/section-label";
import { BETA_FOOTER_NOTE, BETA_SUMMARY } from "~/lib/beta"; import { BETA_FOOTER_NOTE, BETA_SUMMARY } from "~/lib/beta";
import { Badge } from "~/components/ui/badge";
import { import {
Card, Card,
CardContent, CardContent,
@@ -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 =
@@ -105,20 +106,20 @@ const impact = [
]; ];
const FOR_HEALTH_SYSTEMS = const FOR_HEALTH_SYSTEMS =
"Out-of-hospital care is the blind spot. A health system loses sight of the patient the moment they leave, and only learns what happened when they return, often in worse shape. Medscribe turns that gap into an ongoing connection the system can offer under its own brand."; "Epic is the definitive record of care inside the network. What no institutional EHR is built to be is the record the patient owns and carries everywhere else: the out-of-network ER, the specialist on another Epic instance, the months between visits at home. That out-of-hospital stretch is a structural blind spot, not a flaw in any one system. Medscribe fills it on the patient's own device, and — only with the patient's consent — hands that context back, so the gap becomes an ongoing connection instead of a black box.";
const forHealthSystems = [ const forHealthSystems = [
{ {
title: "An ongoing touchstone", title: "Reaches where the EHR can't",
body: "The record lives on the patient's device; they choose what to share back, securely and revocably. That gives the system a consented window into care between visits, and a reason patients return instead of going dark.", body: "The record lives on the patient's device and travels with them: the out-of-network ER, the covering specialist, the weeks at home between visits. It stays current exactly where an institutional system loses sight of the patient.",
}, },
{ {
title: "A branded surface, not a cost center", title: "Feeds context back into Epic",
body: "Skinned per provider, it is a patient-facing presence the system owns, working the levers it is measured on: keeping patients in-network, improving medication adherence and follow-through, and reducing avoidable readmissions.", body: "With the patient's consent, the out-of-network and between-visit context Medscribe captures returns into the workflows you already run. It raises the value of the EHR investment rather than competing for budget or attention.",
}, },
{ {
title: "Complements the EHR, doesn't compete", title: "Patient-owned, so it persists",
body: "It integrates with Epic or any EHR rather than replacing it. The patient stays in control, and because the record is patient-owned, it persists even if the system changes EHRs.", body: "A branded, patient-facing surface that works the levers systems are measured on — in-network retention, medication adherence, avoidable readmissions. Because the record belongs to the patient, it holds even if the system changes EHRs.",
}, },
]; ];
@@ -132,6 +133,11 @@ const printLightTokens = {
} as CSSProperties; } as CSSProperties;
export default function ExecutiveSummaryPage() { export default function ExecutiveSummaryPage() {
// The home page is now the executive summary (single-page site). This deep
// page is retired but kept in place — its content (incl. the print one-pager)
// is a one-line revert away if we want a longer version back.
redirect("/");
return ( return (
<> <>
{/* ===================== SCREEN ===================== */} {/* ===================== SCREEN ===================== */}
@@ -212,33 +218,6 @@ export default function ExecutiveSummaryPage() {
</Card> </Card>
))} ))}
</div> </div>
</section>
{/* Where we fit */}
<section className="space-y-5">
<SectionLabel>Where we fit</SectionLabel>
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
The field is full of slices.
</h2>
<p className="max-w-3xl leading-relaxed text-muted-foreground">
Visit recorders, medication apps, record vaults, emergency IDs, and
caregiver tools each solve one part of the problem. None keeps a
private, patient-owned record current day to day and ready to hand
over in a crisis.
</p>
<div className="flex flex-wrap gap-2">
{[
"Visit recorders",
"Medication apps",
"Record vaults",
"Emergency IDs",
"Caregiver tools",
].map((slice) => (
<Badge key={slice} variant="outline">
{slice}
</Badge>
))}
</div>
<Card className="border-primary-button/20 bg-primary-button text-white"> <Card className="border-primary-button/20 bg-primary-button text-white">
<CardContent className="pt-5"> <CardContent className="pt-5">
<p className="leading-relaxed"> <p className="leading-relaxed">
@@ -323,17 +302,9 @@ export default function ExecutiveSummaryPage() {
<PrintButton size="lg" className="shrink-0" /> <PrintButton size="lg" className="shrink-0" />
</section> </section>
<div className="space-y-3 border-t border-border-soft pt-6"> <p className="border-t border-border-soft pt-6 text-sm text-muted-foreground">
<p className="max-w-2xl text-sm leading-relaxed text-muted-foreground italic"> Sean O&apos;Connor
I built the first version of Medscribe after watching my own family </p>
try to hold this together for my grandfather. The record scattered
right when we needed it most.
</p>
<p className="text-sm text-muted-foreground">
Sean O&apos;Connor · Bucknell University, Computer Science &amp;
Engineering
</p>
</div>
</div> </div>
{/* ===================== PRINT: dense one-page PDF ===================== */} {/* ===================== PRINT: dense one-page PDF ===================== */}
+268
View File
@@ -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&apos;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&apos;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>
</>
);
}
+1
View File
@@ -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 -1
View File
@@ -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 />
</> </>
); );
+477 -285
View File
@@ -1,330 +1,522 @@
import type { Metadata } from "next";
import { import {
AlertTriangle, ArrowRight,
ChevronRight, Check,
ClipboardList, ContactRound,
HeartPulse, KeyRound,
Lock, LockKeyhole,
Mic, ScanLine,
Pill, ShieldCheck,
Shield, Smartphone,
Users,
} from "lucide-react"; } from "lucide-react";
import Link from "next/link";
import { FeatureShowcase } from "~/components/feature-showcase"; import { ProductDevice } from "~/components/product-device";
import { PhoneMockup } from "~/components/phone-mockup";
import { SectionLabel } from "~/components/section-label";
import {
BETA_CTA_BODY,
BETA_CTA_TITLE,
BETA_INVITE_MAILTO,
} from "~/lib/beta";
import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { import { productClaims, productRelease } from "~/content/product";
Card, import { BETA_INVITE_MAILTO } from "~/lib/beta";
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "~/components/ui/card";
const quickActions = [ export const metadata: Metadata = {
{ title: "Carry the context when care gets complicated",
icon: HeartPulse, description:
title: "Know Me card", "Medscribe is a private, patient-controlled beta that helps keep visits, medications, and critical context together on your device.",
meta: "Baseline, conditions, allergies", };
featured: true,
},
{
icon: Pill,
title: "Time-critical meds",
meta: "Schedules caregivers can trust",
},
{
icon: Mic,
title: "Visit capture",
meta: "Consent-gated, on-device",
},
{
icon: Users,
title: "Caregiver handoff",
meta: "One current shared truth",
},
];
const pillars = [ const visitMoments = [
{ {
icon: ClipboardList, number: "01",
title: "Keep the record current", eyebrow: "Visits stay findable",
body: "Capture visits, scan labels, and keep conditions, medications, allergies, contacts, and caregiver notes in one portable record.", 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: AlertTriangle, number: "02",
title: "Make the critical context obvious", eyebrow: "Words stay checkable",
body: "The emergency view prioritizes the facts clinicians need fast: baseline, time-critical meds, active conditions, allergies, and who to call.", 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: Shield, number: "03",
title: "Stay private by architecture", eyebrow: "The next step stays visible",
body: "Transcription, OCR, and medication chat run locally. No cloud AI processing. Sharing should be explicit, revocable, and encrypted.", 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 impact = [ const proofStats = [
{ {
title: "Fewer blank handoffs", value: productRelease.drugFacts.toLocaleString("en-US"),
body: "When a caregiver is overwhelmed, the important facts are already assembled: conditions, medications, allergies, baseline, contacts, and recent changes.", label: "bundled medication records",
}, },
{ {
title: "Safer medication moments", value: productRelease.rxNormTerms.toLocaleString("en-US"),
body: "Time-critical schedules and dose history stay visible, so a missed or delayed medication is easier to catch before it becomes a crisis.", label: "RxNorm terms for matching",
}, },
{ {
title: "More confident families", value: productRelease.interactionPairs.toLocaleString("en-US"),
body: "Caregivers can see what changed, where it came from, and what still needs a clinician or pharmacist instead of guessing from memory.", 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,
title: "Facts do not come from model memory",
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() {
const today = new Date().toLocaleDateString("en-US", {
weekday: "long",
month: "long",
day: "numeric",
});
return ( return (
<div className="mx-auto max-w-6xl space-y-14 px-4 py-8 sm:px-6 sm:py-12 lg:space-y-20 lg:py-14"> <>
<section className="relative isolate min-h-[calc(100svh-4rem)] overflow-hidden px-4 pt-14 sm:px-6 sm:pt-20 lg:pt-24">
{/* Hero */} <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" />
<section className="grid items-center gap-8 lg:grid-cols-[minmax(0,1fr)_minmax(230px,280px)] lg:gap-14"> <div className="mx-auto max-w-7xl text-center">
<div className="space-y-6"> <div className="animate-fade-in-up flex items-center justify-center gap-3">
<div className="animate-fade-in-up space-y-5"> <span className="bg-success-light text-success rounded-full px-3 py-1.5 text-xs font-bold tracking-[0.12em] uppercase">
<SectionLabel>Medscribe · Private complex-care context</SectionLabel> {productRelease.stage}
<h1 className="max-w-2xl font-serif text-4xl leading-[1.05] font-semibold tracking-tight text-foreground sm:text-5xl lg:text-6xl"> </span>
Be known when care gets complicated <span className="text-muted-foreground text-xs font-semibold tracking-[0.12em] uppercase">
</h1> {productRelease.platforms}
<p className="max-w-xl text-base leading-relaxed text-muted-foreground sm:text-lg"> </span>
A private medical context app for families managing complex care.
It keeps visits, medications, baseline, contacts, allergies, and
emergency facts ready before the chart catches up.
</p>
<p className="text-sm text-muted-foreground">{today}</p>
</div> </div>
<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]">
<div className="animate-fade-in-up-delay-1 flex flex-wrap gap-3"> Carry the context.
</h1>
<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">
Visits, medications, and the critical details between themkept
together on the device you carry.
</p>
<div className="animate-fade-in-up-delay-3 mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
<Button asChild size="lg"> <Button asChild size="lg">
<a href="/executive-summary"> <Link href="/beta">
Read executive summary Request beta access
<ChevronRight className="size-4" /> <ArrowRight className="size-4" aria-hidden="true" />
</a> </Link>
</Button> </Button>
<Button asChild variant="outline" size="lg"> <Button asChild size="lg" variant="outline">
<a href="#features">See how it works</a> <Link href="/how-it-works">See how it works</Link>
</Button> </Button>
</div> </div>
<div className="animate-fade-in-up-delay-2 flex flex-wrap gap-2">
<Badge variant="success">Beta available</Badge>
<Badge variant="outline">iOS · TestFlight</Badge>
<Badge variant="outline">Android · Play internal</Badge>
<Badge variant="outline">Emergency-ready</Badge>
<Badge variant="outline">No cloud AI</Badge>
</div>
</div> </div>
<div className="animate-fade-in-up-delay-3 flex justify-center lg:justify-end"> <div className="relative mx-auto mt-12 h-[36rem] max-w-5xl sm:h-[46rem] lg:mt-8 lg:h-[54rem]">
<div className="flex w-fit items-start justify-center"> <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]">
<PhoneMockup <ProductDevice
src="/screenshots/home.png" src="/screenshots/pitch/summary-natural.webp"
alt="Medscribe home dashboard" alt="Medscribe visit summary in a natural titanium iPhone product bezel"
sizes="(min-width: 1024px) 416px, (min-width: 640px) 352px, 272px"
priority priority
width={190} />
</div>
<div className="absolute top-0 left-1/2 z-10 w-[18rem] -translate-x-[14%] rotate-5 sm:w-[23rem] lg:w-[28rem]">
<ProductDevice
src="/screenshots/pitch/home-black.webp"
alt="Medscribe home screen in a black titanium iPhone product bezel"
sizes="(min-width: 1024px) 448px, (min-width: 640px) 368px, 288px"
priority
/>
</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>
</div>
</section>
<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">
<div className="mx-auto max-w-6xl text-center">
<p className="text-primary text-xs font-bold tracking-[0.2em] uppercase">
The missing picture
</p>
<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&apos;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&apos;s memory. Medscribe
is the patient-held thread between those moments.
</p>
</div>
</section>
<section className="pitch-lazy px-4 py-24 sm:px-6 lg:py-36">
<div className="mx-auto grid max-w-7xl gap-16 lg:grid-cols-[0.78fr_1.22fr] lg:gap-24">
<div className="lg:sticky lg:top-28 lg:h-fit">
<p className="text-primary text-xs font-bold tracking-[0.2em] uppercase">
After the visit
</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>
<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>
<ol className="space-y-8 sm:space-y-12">
{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 viewnot 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>
</div> </div>
</section> </section>
{/* Strategic spine */} <section className="pitch-lazy overflow-hidden px-4 py-24 sm:px-6 lg:py-36">
<section className="animate-fade-in-up space-y-5"> <div className="mx-auto max-w-7xl">
<SectionLabel>The story</SectionLabel> <div className="grid items-center gap-16 lg:grid-cols-[0.92fr_1.08fr] lg:gap-24">
<div className="grid gap-6 lg:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)] lg:items-start"> <div className="relative mx-auto w-[min(80vw,28rem)]">
<div className="space-y-3"> <div className="bg-primary-light/70 absolute inset-[12%_-12%_8%] -z-10 rounded-[4rem] blur-2xl" />
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl"> <ProductDevice
The story scatters right when it matters src="/screenshots/pitch/know-me-natural.webp"
</h2> alt="Medscribe Know Me handoff record in a natural titanium iPhone product bezel"
<p className="max-w-xl leading-relaxed text-muted-foreground"> sizes="(min-width: 1024px) 448px, (min-width: 640px) 400px, 80vw"
A daughter remembers the medication schedule. A spouse knows what />
normal looks like. A specialist mentioned a change three visits ago. </div>
In a crisis, those details are scattered across people, portals, and <div>
memory. <p className="text-primary text-xs font-bold tracking-[0.2em] uppercase">
</p> At the handoff
<p className="max-w-xl text-sm leading-relaxed text-muted-foreground/80 italic"> </p>
I built the first version of Medscribe after watching my own family <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]">
try to hold this together for my grandfather. The record scattered Bring the picture when the chart is incomplete.
right when we needed it most. </h2>
</p> <p className="text-muted-foreground mt-8 max-w-xl text-lg leading-relaxed sm:text-xl">
</div> Know Me gathers selected medications, allergies, conditions,
<div className="rounded-lg border border-border-soft bg-surface p-5 shadow-[0_1px_4px_rgba(0,0,0,0.05)]"> baseline, and contacts into one readable snapshot. Freshness
<div className="flex items-start gap-3"> signals show what may need another look.
<HeartPulse className="mt-0.5 size-5 shrink-0 text-primary" /> </p>
<div className="space-y-2"> <div className="mt-9 grid gap-4 sm:grid-cols-2">
<h3 className="font-serif text-xl font-semibold tracking-tight"> <div className="border-border-soft bg-surface rounded-2xl border p-5">
One current story <ScanLine
</h3> className="text-primary size-6"
<p className="text-sm leading-relaxed text-muted-foreground"> aria-hidden="true"
Medscribe gives the family a private place to keep that context />
alive: current meds, timing, baseline, allergies, recent visit <p className="text-foreground mt-4 font-semibold">
summaries, emergency contacts, and the source trail behind each Quick handoff
fact. </p>
</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>
</div> </div>
</div>
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4"> <div className="mt-24 overflow-hidden rounded-[2.5rem] bg-[#0f4f4b] p-7 text-white sm:p-10 lg:p-14">
{quickActions.map((action) => { <div className="grid gap-12 lg:grid-cols-[0.85fr_1.15fr] lg:items-center">
const Icon = action.icon; <div>
<KeyRound className="size-7 text-teal-200" aria-hidden="true" />
return ( <h3 className="mt-5 font-serif text-4xl leading-tight font-semibold sm:text-5xl">
<Card Share a snapshot. Not an invisible feed.
key={action.title} </h3>
className={`card-hover ${action.featured ? "border-primary-button/20 bg-primary-button text-white" : ""}`} <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
<CardHeader className="gap-3"> export. The caregiver imports a local copy on their device.
<div </p>
className={`flex size-10 items-center justify-center rounded-[10px] ${ </div>
action.featured ? "bg-white/18" : "bg-primary-light" <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"
> >
<Icon <span className="flex size-8 items-center justify-center rounded-full bg-white/12 text-xs font-bold">
className={`size-5 ${action.featured ? "text-white" : "text-primary"}`} {number}
/> </span>
</div> <p className="mt-8 font-serif text-xl font-semibold">
<div> {title}
<CardTitle className={action.featured ? "text-white" : ""}> </p>
{action.title} <p className="mt-2 text-sm leading-relaxed text-teal-50/65">
</CardTitle> {body}
<CardDescription </p>
className={action.featured ? "text-white/72" : ""} </li>
> ))}
{action.meta} </ol>
</CardDescription>
</div>
</CardHeader>
</Card>
);
})}
</div>
</section>
{/* Interactive feature showcase */}
<section id="features" className="animate-fade-in-up scroll-mt-24 space-y-5">
<div className="space-y-2">
<SectionLabel>The record</SectionLabel>
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
The pieces that keep the story current
</h2>
<p className="max-w-2xl text-muted-foreground">
Visits, medications, reminders, search, and questions all point toward
the same outcome: a record the family can trust when the stakes rise.
</p>
</div>
<FeatureShowcase />
</section>
{/* Privacy */}
<section id="privacy" className="animate-fade-in-up scroll-mt-24 space-y-6">
<div className="space-y-3">
<SectionLabel>Why this is different</SectionLabel>
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
Built for the family record, not another cloud inbox
</h2>
<p className="max-w-2xl text-muted-foreground">
The crowded space already has visit recorders, medication apps, and
caregiver portals. Medscribe&apos;s wedge is the private, portable
context layer families control before an institution is ready.
</p>
</div>
<div className="grid gap-4 md:grid-cols-3">
{pillars.map((pillar) => {
const Icon = pillar.icon;
return (
<Card key={pillar.title} className="card-hover">
<CardHeader>
<div className="flex size-10 items-center justify-center rounded-[10px] bg-primary-light">
<Icon className="size-5 text-primary" />
</div>
<CardTitle>{pillar.title}</CardTitle>
<CardDescription>{pillar.body}</CardDescription>
</CardHeader>
</Card>
);
})}
</div>
<Card className="border-primary/15 bg-primary-light/40">
<CardContent className="flex items-start gap-4 pt-5">
<Lock className="mt-0.5 size-5 shrink-0 text-primary" />
<p className="text-sm leading-relaxed text-foreground">
All AI models run on your device. No health data is uploaded for
processing. The medication chatbot stays in drug-information
territory. It defers dosing and diagnosis questions to your
clinician or pharmacist.
</p>
</CardContent>
</Card>
</section>
{/* Impact */}
<section id="impact" className="animate-fade-in-up scroll-mt-24 space-y-6">
<div className="space-y-3">
<SectionLabel>Potential impact</SectionLabel>
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
Less panic, more context, better handoffs
</h2>
<p className="max-w-2xl text-muted-foreground">
The goal is simple: make the person easier to understand at the exact
moment their care team has the least context.
</p>
</div>
<div className="grid gap-4 md:grid-cols-3">
{impact.map((item) => (
<Card key={item.title} className="card-hover">
<CardHeader>
<CardTitle>{item.title}</CardTitle>
<CardDescription>{item.body}</CardDescription>
</CardHeader>
</Card>
))}
</div>
</section>
{/* CTA */}
<section className="animate-fade-in-up">
<Card className="overflow-hidden border-primary-button/20 bg-primary-button text-white">
<CardHeader className="gap-4 sm:flex-row sm:items-center sm:justify-between">
<div className="space-y-2">
<CardTitle className="text-white">{BETA_CTA_TITLE}</CardTitle>
<CardDescription className="text-white/75">
{BETA_CTA_BODY}
</CardDescription>
</div> </div>
<Button <p className="mt-8 border-t border-white/12 pt-6 text-xs leading-relaxed text-teal-50/60">
asChild Manual todaynot live sync or a household cloud account.
size="lg" Import-side sender trust enforcement is still being hardened;
variant="outline" revocation cannot recall a copy already exported.
className="border-white/30 bg-white text-primary-button hover:bg-white/90" </p>
> </div>
<a href={BETA_INVITE_MAILTO}>Request an invite</a> </div>
</Button>
</CardHeader>
</Card>
</section> </section>
</div> <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 (
<article
key={principle.title}
className="rounded-[1.75rem] border border-white/12 bg-white/[0.045] p-7"
>
<Icon className="size-6 text-teal-200" aria-hidden="true" />
<h3 className="mt-8 font-serif text-2xl font-semibold">
{principle.title}
</h3>
<p className="mt-4 text-sm leading-relaxed text-slate-400">
{principle.body}
</p>
</article>
);
})}
</div>
<div className="mt-10 text-center">
<Button asChild variant="secondary">
<Link href="/privacy">
Read privacy & evidence
<ArrowRight className="size-4" aria-hidden="true" />
</Link>
</Button>
</div>
</div>
</section>
<section className="pitch-lazy flex min-h-[82svh] items-center px-4 py-24 sm:px-6 lg:py-32">
<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%)]">
<p className="text-primary text-xs font-bold tracking-[0.2em] uppercase">
The right beta participant
</p>
<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]">
Help build the record people can actually carry.
</h2>
<p className="text-muted-foreground mx-auto mt-8 max-w-2xl text-lg leading-relaxed sm:text-xl">
This is an internal beta. Expect close feedback, manual review,
rough edges, and possible resetsnot guaranteed continuity.
</p>
<ul className="mx-auto mt-9 grid max-w-4xl gap-3 text-left md:grid-cols-3">
{betaFit.map((item) => (
<li
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"
>
<span className="bg-primary-light mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full">
<Check className="text-primary size-3" aria-hidden="true" />
</span>
{item}
</li>
))}
</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>
</>
); );
} }
+388
View File
@@ -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&apos;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>
</>
);
}
+33
View File
@@ -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>
</>
);
}
+11 -5
View File
@@ -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 />
+19
View File
@@ -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",
],
},
};
}
+308
View File
@@ -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&apos;s whole
medical picture.
</h1>
<p className="mt-5 text-[15px] leading-[1.55] text-slate-600">
Real medical AI on the patient&apos;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&apos;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&apos;s care.
</p>
</div>
</section>
</PrintPage>
</div>
);
}
+116 -96
View File
@@ -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 persons 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>
+381
View File
@@ -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&apos;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&apos;s device and under the patient&apos;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&amp;A run on the phone.
Caregiver sharing is end-to-end encrypted, so a server is not
asked to read the patient&apos;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&apos;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 &ldquo;AI in
healthcare,&rdquo; 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&apos;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&apos;s care. The product is live in beta on iOS and
Android today.
</p>
</div>
</section>
</PrintPage>
</div>
);
}
+20 -30
View File
@@ -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>
); );
} }
+39
View File
@@ -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,
)}
/>
);
}
+15 -2
View File
@@ -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>
); );
+55 -7
View File
@@ -1,23 +1,71 @@
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-6xl flex-col gap-4 px-4 py-10 text-sm text-muted-foreground sm:px-6 sm:flex-row sm:items-center sm:justify-between"> <div className="mx-auto 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&apos;s verification.
</p> </p>
</div> </div>
<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> <div>
<p> <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&apos;Connor Sean O&apos;Connor
</a> </a>
</p> </p>
<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">
&copy; {new Date().getFullYear()} Hadlock Holdings LLC
</p>
</div> </div>
</div> </div>
</footer> </footer>
+60 -119
View File
@@ -1,133 +1,74 @@
"use client"; import { Menu } from "lucide-react";
import { useState } from "react";
import { FileText, Home, Menu, X } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { usePathname } from "next/navigation";
import { MedscribeLogo } from "~/components/medscribe-logo"; import { MedscribeLogo } from "~/components/medscribe-logo";
import { cn } from "~/lib/utils"; import { Button } from "~/components/ui/button";
const navLinks = [ const navigation = [
{ href: "/", label: "About", icon: Home }, { href: "/how-it-works", label: "How it works" },
{ href: "/executive-summary", label: "Summary", icon: FileText }, { href: "/privacy", label: "Privacy & evidence" },
]; ] as const;
export function SiteHeader() { export function SiteHeader() {
const pathname = usePathname();
const [isOpen, setIsOpen] = useState(false);
return ( return (
<header className="print-hidden sticky top-3 z-50 px-3 sm:px-6"> <header className="print-hidden 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="flex h-14 items-center justify-between gap-4 rounded-full border border-border-soft/80 bg-canvas/82 px-4 shadow-[0_10px_30px_rgba(15,23,42,0.08)] backdrop-blur-md sm:px-5"> <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="flex items-center text-foreground transition-opacity hover:opacity-80" aria-label="Medscribe home"
aria-label="Medscribe home"
onClick={() => setIsOpen(false)}
>
<MedscribeLogo className="h-7 w-[88px]" />
</Link>
{/* Desktop nav */}
<nav className="hidden items-center gap-6 text-sm sm:flex">
{navLinks.map((link) => {
const isActive = link.href === pathname;
return (
<Link
key={link.href}
href={link.href}
aria-current={isActive ? "page" : undefined}
className={cn(
"relative py-1 transition-colors",
isActive
? "text-foreground"
: "text-muted-foreground hover:text-foreground",
)}
>
{link.label}
<span
className={cn(
"absolute inset-x-0 -bottom-0.5 h-0.5 origin-center rounded-full bg-primary transition-transform duration-200",
isActive ? "scale-x-100" : "scale-x-0",
)}
aria-hidden
/>
</Link>
);
})}
</nav>
{/* Mobile menu toggle */}
<button
type="button"
onClick={() => setIsOpen((v) => !v)}
className="relative size-6 text-muted-foreground transition-colors hover:text-foreground focus:outline-none sm:hidden"
aria-label={isOpen ? "Close menu" : "Open menu"}
aria-expanded={isOpen}
>
<Menu
className={cn(
"absolute inset-0 transition-opacity duration-200",
isOpen ? "opacity-0" : "opacity-100",
)}
/>
<X
className={cn(
"absolute inset-0 transition-opacity duration-200",
isOpen ? "opacity-100" : "opacity-0",
)}
/>
</button>
</div>
{/* Mobile dropdown */}
<div
className={cn(
"absolute inset-x-0 top-16 overflow-hidden rounded-2xl border border-border-soft/80 bg-canvas/95 shadow-[0_10px_30px_rgba(15,23,42,0.12)] backdrop-blur-md transition-all duration-200 sm:hidden",
isOpen
? "pointer-events-auto opacity-100"
: "pointer-events-none -translate-y-1 opacity-0",
)}
> >
<nav className="flex flex-col p-2"> <MedscribeLogo className="h-7 w-auto" />
{navLinks.map((link) => { <span className="font-serif text-lg font-semibold tracking-tight">
const isActive = link.href === pathname; Medscribe
const Icon = link.icon; </span>
</Link>
return ( <nav
<Link aria-label="Primary navigation"
key={link.href} className="ml-auto hidden items-center gap-1 md:flex"
href={link.href} >
aria-current={isActive ? "page" : undefined} {navigation.map((item) => (
onClick={() => setIsOpen(false)} <Link
className={cn( key={item.href}
"flex items-center gap-3 rounded-xl px-3 py-2.5 text-sm font-medium transition-colors", href={item.href}
isActive 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"
? "bg-primary-light/60 text-foreground" >
: "text-muted-foreground hover:bg-surface-soft hover:text-foreground", {item.label}
)} </Link>
> ))}
<Icon className="size-4" /> <Button asChild size="sm" className="ml-2">
{link.label} <Link href="/beta">Request beta access</Link>
</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> </nav>
</div> </details>
</div> </div>
{/* Backdrop */}
<div
className={cn(
"fixed inset-0 -z-10 bg-canvas/30 backdrop-blur-sm transition-opacity duration-200 sm:hidden",
isOpen ? "opacity-100" : "pointer-events-none opacity-0",
)}
onClick={() => setIsOpen(false)}
aria-hidden
/>
</header> </header>
); );
} }
+1 -1
View File
@@ -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: {
+308
View File
@@ -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&apos;s whole
medical picture.
</h1>
<p className="mt-5 text-[15px] leading-[1.55] text-slate-600">
Real medical AI on the patient&apos;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&apos;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&apos;s care.
</p>
</div>
</section>
</PrintPage>
</div>
);
}
+71
View File
@@ -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;
+4 -4
View File
@@ -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).";
+156 -21
View File
@@ -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;
} }
} }