Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81109f36ea | ||
|
|
d3c30859b2 | ||
|
|
2f8dcd66ad |
@@ -20,6 +20,7 @@ next-env.d.ts
|
||||
|
||||
# production
|
||||
/build
|
||||
/output/
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
@@ -43,4 +44,4 @@ yarn-error.log*
|
||||
*.tsbuildinfo
|
||||
|
||||
# idea files
|
||||
.idea
|
||||
.idea
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# 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`).
|
||||
|
||||
@@ -15,6 +17,26 @@ bun run dev
|
||||
|
||||
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)
|
||||
|
||||
Set in `.env` (see `.env.example`):
|
||||
@@ -31,7 +53,10 @@ bun run build
|
||||
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
|
||||
|
||||
@@ -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.
|
||||
2. Capture each screen (⌘S in Simulator saves to Desktop; `adb exec-out screencap -p` on Android).
|
||||
3. Drop PNGs into `public/screenshots/` and update paths in `src/app/page.tsx` / `src/components/feature-showcase.tsx`.
|
||||
3. Drop PNGs into `public/screenshots/` and update paths in the relevant route
|
||||
or `src/components/feature-showcase.tsx`.
|
||||
|
||||
The frame dimensions assume tall iPhone screenshots and are controlled in `src/components/phone-mockup.tsx`. Use `captureMode="simulator"` only for old captures that already include device chrome.
|
||||
The CSS frame dimensions assume tall iPhone screenshots and are controlled in
|
||||
`src/components/phone-mockup.tsx`. Use `captureMode="simulator"` only for old
|
||||
captures that already include device chrome. Homepage product images are
|
||||
composited separately at 810 × 1656 with transparent backgrounds and rendered
|
||||
through `src/components/product-device.tsx`.
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"postcss": "^8.5.3",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"puppeteer-core": "^25.4.0",
|
||||
"tailwindcss": "^4.0.15",
|
||||
"typescript": "^5.8.2",
|
||||
"typescript-eslint": "^8.27.0",
|
||||
@@ -221,6 +222,8 @@
|
||||
|
||||
"@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-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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"devtools-protocol": ["devtools-protocol@0.0.1653615", "", {}, "sha512-pGVkY3T/qXxAp2nFPodwYqOevk6ncNMSmvL8QfRCx5ZWGd6Vor7AFNmyaA8Zs6uJyP1QAfjuLandCgvSix1BNA=="],
|
||||
|
||||
"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=="],
|
||||
@@ -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=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"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=="],
|
||||
@@ -537,6 +550,10 @@
|
||||
|
||||
"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-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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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.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=="],
|
||||
|
||||
"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-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-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-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=="],
|
||||
|
||||
"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-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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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-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=="],
|
||||
|
||||
"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-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=="],
|
||||
|
||||
"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/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=="],
|
||||
|
||||
"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=="],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"postcss": "^8.5.3",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"puppeteer-core": "^25.4.0",
|
||||
"tailwindcss": "^4.0.15",
|
||||
"typescript": "^5.8.2",
|
||||
"typescript-eslint": "^8.27.0"
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="2550" height="3300" viewBox="0 0 850 1100" preserveAspectRatio="xMidYMin slice">
|
||||
<defs>
|
||||
<pattern id="grid" width="28" height="28" patternUnits="userSpaceOnUse">
|
||||
<path d="M 28 0 L 0 0 0 28" fill="none" stroke="#0f172a" stroke-opacity="0.055" stroke-width="1"/>
|
||||
</pattern>
|
||||
<linearGradient id="wash" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#ccfbf1" stop-opacity="0.5"/>
|
||||
<stop offset="100%" stop-color="#ccfbf1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="850" height="1100" fill="#f8fafc"/>
|
||||
<rect width="850" height="1100" fill="url(#grid)"/>
|
||||
<rect width="850" height="260" fill="url(#wash)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 722 B |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 63 KiB |
@@ -0,0 +1,108 @@
|
||||
// Capture the long-form homepage in both system color schemes.
|
||||
//
|
||||
// bun scripts/capture-homepage.mjs [baseUrl] [outputDirectory]
|
||||
//
|
||||
// Requires a running local server and Google Chrome. If SITE_PASSWORD is set,
|
||||
// the script uses the same signed access cookie as the login route.
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import puppeteer from "puppeteer-core";
|
||||
|
||||
const baseUrl = process.argv[2] ?? "http://localhost:3000";
|
||||
const outputDirectory = path.resolve(process.argv[3] ?? "output");
|
||||
const chrome = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
|
||||
const password = process.env.SITE_PASSWORD;
|
||||
|
||||
await fs.mkdir(outputDirectory, { recursive: true });
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
executablePath: chrome,
|
||||
headless: true,
|
||||
});
|
||||
|
||||
async function capture({ name, width, height, colorScheme }) {
|
||||
const page = await browser.newPage();
|
||||
|
||||
try {
|
||||
await page.setViewport({ width, height, deviceScaleFactor: 1 });
|
||||
await page.emulateMediaFeatures([
|
||||
{ name: "prefers-color-scheme", value: colorScheme },
|
||||
{ name: "prefers-reduced-motion", value: "reduce" },
|
||||
]);
|
||||
|
||||
if (password) {
|
||||
const value = crypto
|
||||
.createHmac("sha256", password)
|
||||
.update("medscribe-site-access-v1")
|
||||
.digest("base64");
|
||||
await browser.setCookie({
|
||||
name: "medscribe_site_access",
|
||||
value,
|
||||
url: baseUrl,
|
||||
});
|
||||
}
|
||||
|
||||
await page.goto(baseUrl, {
|
||||
waitUntil: "domcontentloaded",
|
||||
timeout: 60_000,
|
||||
});
|
||||
if (page.url().includes("/login")) {
|
||||
throw new Error("Homepage capture was redirected to /login.");
|
||||
}
|
||||
|
||||
await page.addStyleTag({
|
||||
content:
|
||||
".pitch-lazy{content-visibility:visible!important}*{animation:none!important;transition:none!important}",
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
await document.fonts.ready;
|
||||
for (let y = 0; y < document.documentElement.scrollHeight; y += 700) {
|
||||
window.scrollTo(0, y);
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
}
|
||||
window.scrollTo(0, 0);
|
||||
});
|
||||
await page.waitForFunction(
|
||||
() => Array.from(document.images).every((image) => image.complete),
|
||||
{ timeout: 30_000 },
|
||||
);
|
||||
|
||||
const layout = await page.evaluate(() => ({
|
||||
clientWidth: document.documentElement.clientWidth,
|
||||
scrollWidth: document.documentElement.scrollWidth,
|
||||
scrollHeight: document.documentElement.scrollHeight,
|
||||
}));
|
||||
if (layout.scrollWidth > layout.clientWidth) {
|
||||
throw new Error(
|
||||
`${name} has horizontal overflow: ${layout.scrollWidth}px > ${layout.clientWidth}px`,
|
||||
);
|
||||
}
|
||||
|
||||
const destination = path.join(outputDirectory, `${name}.png`);
|
||||
await page.screenshot({ path: destination, fullPage: true });
|
||||
console.log(`${name}: ${layout.clientWidth} × ${layout.scrollHeight}`);
|
||||
} finally {
|
||||
await page.close();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
for (const colorScheme of ["light", "dark"]) {
|
||||
await capture({
|
||||
name: `homepage-${colorScheme}`,
|
||||
width: 1440,
|
||||
height: 1000,
|
||||
colorScheme,
|
||||
});
|
||||
await capture({
|
||||
name: `homepage-${colorScheme}-mobile`,
|
||||
width: 390,
|
||||
height: 844,
|
||||
colorScheme,
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Render a marketing route to a print-styled PDF, the same way the browser's
|
||||
// "Print PDF" button does (print CSS + designed print-only components).
|
||||
//
|
||||
// bun scripts/render-pdf.mjs <route> <output.pdf> [baseUrl]
|
||||
// bun scripts/render-pdf.mjs /cvs ../docs/business/CVS_SUMMARY.pdf
|
||||
//
|
||||
// Self-authenticates past the SITE_PASSWORD gate by computing the same signed
|
||||
// access cookie the login route issues (see src/lib/site-auth.ts). Requires a
|
||||
// running dev/preview server and a local Google Chrome.
|
||||
import crypto from "node:crypto";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import puppeteer from "puppeteer-core";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const route = process.argv[2] ?? "/cvs";
|
||||
const outArg = process.argv[3] ?? "../docs/business/CVS_SUMMARY.pdf";
|
||||
const baseUrl = process.argv[4] ?? "http://localhost:3000";
|
||||
const outPath = path.resolve(__dirname, "..", outArg);
|
||||
|
||||
const CHROME =
|
||||
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
|
||||
const AUTH_COOKIE_NAME = "medscribe_site_access";
|
||||
const ACCESS_PAYLOAD = "medscribe-site-access-v1";
|
||||
|
||||
const password = process.env.SITE_PASSWORD;
|
||||
const cookies = [];
|
||||
if (password) {
|
||||
const token = crypto
|
||||
.createHmac("sha256", password)
|
||||
.update(ACCESS_PAYLOAD)
|
||||
.digest("base64");
|
||||
cookies.push({
|
||||
name: AUTH_COOKIE_NAME,
|
||||
value: token,
|
||||
url: baseUrl,
|
||||
});
|
||||
}
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
executablePath: CHROME,
|
||||
headless: true,
|
||||
});
|
||||
try {
|
||||
const page = await browser.newPage();
|
||||
if (cookies.length) await browser.setCookie(...cookies);
|
||||
const res = await page.goto(`${baseUrl}${route}`, {
|
||||
waitUntil: "networkidle0",
|
||||
timeout: 60_000,
|
||||
});
|
||||
const finalUrl = page.url();
|
||||
if (finalUrl.includes("/login")) {
|
||||
throw new Error(
|
||||
`Redirected to login (${res?.status()}). Check SITE_PASSWORD is set in the env used to run this script.`,
|
||||
);
|
||||
}
|
||||
await page.pdf({
|
||||
path: outPath,
|
||||
printBackground: true,
|
||||
preferCSSPageSize: true,
|
||||
});
|
||||
console.log(`Wrote ${outPath}`);
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
import type { Metadata } from "next";
|
||||
import {
|
||||
ArrowRight,
|
||||
Check,
|
||||
ClipboardCheck,
|
||||
Mail,
|
||||
MessageSquareText,
|
||||
RotateCcw,
|
||||
Smartphone,
|
||||
Users,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { SectionLabel } from "~/components/section-label";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { productRelease } from "~/content/product";
|
||||
import { BETA_INVITE_MAILTO } from "~/lib/beta";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Request beta access",
|
||||
description:
|
||||
"Learn who the Medscribe internal beta is for, what participants should expect, and how to request access without submitting health information.",
|
||||
};
|
||||
|
||||
const expectations = [
|
||||
{
|
||||
icon: ClipboardCheck,
|
||||
title: "Review is part of testing",
|
||||
body: "Check generated summaries, extracted medication fields, and the Know Me view before relying on them.",
|
||||
},
|
||||
{
|
||||
icon: MessageSquareText,
|
||||
title: "Direct feedback",
|
||||
body: "Report confusing language, incorrect extraction, notification behavior, and sharing friction.",
|
||||
},
|
||||
{
|
||||
icon: RotateCcw,
|
||||
title: "Possible resets",
|
||||
body: "This is an internal beta. Builds, local data, and setup may need to be reset as the product changes.",
|
||||
},
|
||||
] as const;
|
||||
|
||||
const testFocus = [
|
||||
"Whether the record still feels current after several weeks",
|
||||
"Medication-label edge cases and correction flow",
|
||||
"Reminder permissions, timing, travel, and daylight-saving behavior",
|
||||
"Caregiver pairing, encrypted export, and import comprehension",
|
||||
"Whether the Know Me handoff is readable and appropriately cautious",
|
||||
"Physical-device model performance and battery or thermal behavior",
|
||||
] as const;
|
||||
|
||||
export default function BetaPage() {
|
||||
return (
|
||||
<>
|
||||
<section className="px-4 pt-16 pb-18 sm:px-6 sm:pt-22">
|
||||
<div className="mx-auto grid max-w-6xl gap-12 lg:grid-cols-[1fr_0.8fr] lg:items-center">
|
||||
<div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge variant="success">{productRelease.stage}</Badge>
|
||||
<Badge variant="outline">{productRelease.platforms}</Badge>
|
||||
</div>
|
||||
<h1 className="text-foreground mt-6 max-w-3xl font-serif text-5xl leading-[1.02] font-semibold tracking-[-0.03em] sm:text-6xl">
|
||||
Test the care context, not just the demo.
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-6 max-w-2xl text-lg leading-relaxed sm:text-xl">
|
||||
We are looking for people managing repeated visits or several
|
||||
medications, especially family caregiver pairs willing to review
|
||||
the record over time and tell us where it fails.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="border-border-soft bg-surface rounded-[2rem] border p-7 shadow-[0_24px_70px_rgba(15,23,42,0.08)] sm:p-9">
|
||||
<div className="bg-primary-light text-primary flex size-12 items-center justify-center rounded-xl">
|
||||
<Mail className="size-6" aria-hidden="true" />
|
||||
</div>
|
||||
<h2 className="text-foreground mt-5 font-serif text-3xl font-semibold">
|
||||
Request access by email
|
||||
</h2>
|
||||
<p className="text-muted-foreground mt-4 text-sm leading-relaxed">
|
||||
The draft asks only for your platform, whether you are testing as
|
||||
a patient, caregiver, or both, and whether you can test with
|
||||
another person.
|
||||
</p>
|
||||
<Button asChild size="lg" className="mt-7 w-full sm:w-auto">
|
||||
<a href={BETA_INVITE_MAILTO}>
|
||||
Draft beta request
|
||||
<ArrowRight className="size-4" aria-hidden="true" />
|
||||
</a>
|
||||
</Button>
|
||||
<p className="bg-surface-soft text-muted-foreground mt-5 rounded-xl p-4 text-xs leading-relaxed">
|
||||
Do not send diagnoses, medication names, visit notes, transcripts,
|
||||
or other health details. Email is not a medical-support or
|
||||
emergency channel.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="border-border-soft border-y bg-[#f4f0e8]/75 px-4 py-20 sm:px-6 lg:py-24 dark:bg-[#151b25]">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<SectionLabel>A good fit</SectionLabel>
|
||||
<h2 className="text-foreground mt-4 max-w-3xl font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||
The beta needs real maintenance and handoff situations.
|
||||
</h2>
|
||||
<div className="mt-10 grid gap-4 md:grid-cols-3">
|
||||
{[
|
||||
{
|
||||
icon: Smartphone,
|
||||
title: "Several moving pieces",
|
||||
body: "You regularly coordinate visits, medication reminders, or follow-ups.",
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: "A caregiver relationship",
|
||||
body: "You can test a deliberate handoff with a trusted family caregiver.",
|
||||
},
|
||||
{
|
||||
icon: Check,
|
||||
title: "Comfort reviewing",
|
||||
body: "You understand generated information can be wrong and will check it.",
|
||||
},
|
||||
].map((item) => {
|
||||
const Icon = item.icon;
|
||||
|
||||
return (
|
||||
<article
|
||||
key={item.title}
|
||||
className="border-border-soft bg-surface rounded-2xl border p-6"
|
||||
>
|
||||
<Icon className="text-primary size-6" aria-hidden="true" />
|
||||
<h3 className="text-foreground mt-5 font-serif text-2xl font-semibold">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="text-muted-foreground mt-3 text-sm leading-relaxed">
|
||||
{item.body}
|
||||
</p>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="px-4 py-20 sm:px-6 lg:py-28">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<div className="grid gap-12 lg:grid-cols-[0.8fr_1.2fr]">
|
||||
<div>
|
||||
<SectionLabel>What to expect</SectionLabel>
|
||||
<h2 className="text-foreground mt-4 font-serif text-4xl font-semibold tracking-tight">
|
||||
This is collaborative product testing.
|
||||
</h2>
|
||||
<p className="text-muted-foreground mt-5 leading-relaxed">
|
||||
Availability is controlled and continuity is not guaranteed.
|
||||
Medscribe is not emergency care, diagnosis, dosing guidance, or
|
||||
a replacement for your care team.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-3">
|
||||
{expectations.map((item) => {
|
||||
const Icon = item.icon;
|
||||
|
||||
return (
|
||||
<article
|
||||
key={item.title}
|
||||
className="border-border-soft bg-surface rounded-2xl border p-5"
|
||||
>
|
||||
<Icon className="text-primary size-5" aria-hidden="true" />
|
||||
<h3 className="text-foreground mt-4 font-serif text-xl font-semibold">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="text-muted-foreground mt-3 text-sm leading-relaxed">
|
||||
{item.body}
|
||||
</p>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-16 rounded-[2rem] bg-[#0f4f4b] p-7 text-white sm:p-10">
|
||||
<div className="grid gap-10 lg:grid-cols-[0.7fr_1.3fr]">
|
||||
<div>
|
||||
<p className="text-xs font-bold tracking-[0.14em] text-teal-100 uppercase">
|
||||
Current test focus
|
||||
</p>
|
||||
<h2 className="mt-4 font-serif text-3xl font-semibold">
|
||||
The questions we are trying to answer
|
||||
</h2>
|
||||
</div>
|
||||
<ul className="grid gap-3 sm:grid-cols-2">
|
||||
{testFocus.map((item) => (
|
||||
<li
|
||||
key={item}
|
||||
className="flex gap-3 rounded-xl border border-white/15 bg-white/8 p-4 text-sm leading-relaxed text-teal-50/85"
|
||||
>
|
||||
<Check
|
||||
className="mt-0.5 size-4 shrink-0 text-teal-100"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-border-soft mt-12 flex flex-col items-start justify-between gap-5 border-t pt-10 sm:flex-row sm:items-center">
|
||||
<div>
|
||||
<h2 className="text-foreground font-serif text-2xl font-semibold">
|
||||
Want the technical boundaries first?
|
||||
</h2>
|
||||
<p className="text-muted-foreground mt-2 text-sm">
|
||||
See what stays local, how sharing works, and the current
|
||||
evidence.
|
||||
</p>
|
||||
</div>
|
||||
<Button asChild variant="outline">
|
||||
<Link href="/privacy">Read privacy & evidence</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { Metadata } from "next";
|
||||
|
||||
import { NorthwellPrintSummary } from "~/components/northwell-print-summary";
|
||||
import { PrintButton } from "~/components/print-button";
|
||||
import { SectionLabel } from "~/components/section-label";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Northwell executive summary",
|
||||
robots: { index: false, follow: false, nocache: true },
|
||||
};
|
||||
|
||||
export default function NorthwellBriefPage() {
|
||||
return (
|
||||
<>
|
||||
<NorthwellPrintSummary />
|
||||
<div className="print-hidden mx-auto max-w-2xl px-4 py-16 text-center sm:px-6 lg:py-24">
|
||||
<SectionLabel>Executive summary · Northwell Health</SectionLabel>
|
||||
<h1 className="text-foreground mt-4 font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||
The health-system executive summary
|
||||
</h1>
|
||||
<p className="text-muted-foreground mx-auto mt-5 max-w-xl leading-relaxed">
|
||||
This private, search-excluded route preserves the existing three-page
|
||||
print document. Use Print PDF (or Cmd+P, then Save as PDF) to export
|
||||
it.
|
||||
</p>
|
||||
<div className="mt-8 flex justify-center">
|
||||
<PrintButton size="lg" />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { Metadata } from "next";
|
||||
|
||||
import { CvsPrintSummary } from "~/components/cvs-print-summary";
|
||||
import { PrintButton } from "~/components/print-button";
|
||||
import { SectionLabel } from "~/components/section-label";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "CVS executive summary",
|
||||
robots: { index: false, follow: false, nocache: true },
|
||||
};
|
||||
|
||||
export default function CvsSummaryPage() {
|
||||
return (
|
||||
<>
|
||||
<CvsPrintSummary />
|
||||
<div className="print-hidden mx-auto max-w-2xl px-4 py-16 text-center sm:px-6 lg:py-24">
|
||||
<SectionLabel>Executive summary · CVS Health</SectionLabel>
|
||||
<h1 className="text-foreground mt-4 font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||
The pharmacy executive summary
|
||||
</h1>
|
||||
<p className="text-muted-foreground mx-auto mt-5 max-w-xl leading-relaxed">
|
||||
This page is formatted as a two-page print document: the medication
|
||||
record that moves adherence, keeps refills, and lowers cost of care.
|
||||
Use Print PDF (or Cmd+P, then Save as PDF) to export it.
|
||||
</p>
|
||||
<div className="mt-8 flex justify-center">
|
||||
<PrintButton size="lg" />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { CSSProperties } from "react";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import { redirect } from "next/navigation";
|
||||
import Image from "next/image";
|
||||
|
||||
import { MedscribeLogo } from "~/components/medscribe-logo";
|
||||
@@ -20,6 +21,7 @@ export const metadata: Metadata = {
|
||||
title: "Executive Summary",
|
||||
description:
|
||||
"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 =
|
||||
@@ -131,6 +133,11 @@ const printLightTokens = {
|
||||
} as CSSProperties;
|
||||
|
||||
export default function ExecutiveSummaryPage() {
|
||||
// The home page is now the executive summary (single-page site). This deep
|
||||
// page is retired but kept in place — its content (incl. the print one-pager)
|
||||
// is a one-line revert away if we want a longer version back.
|
||||
redirect("/");
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* ===================== SCREEN ===================== */}
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
import type { Metadata } from "next";
|
||||
import {
|
||||
ArrowRight,
|
||||
Camera,
|
||||
Check,
|
||||
FileText,
|
||||
Keyboard,
|
||||
Mic,
|
||||
ShieldAlert,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { PhoneMockup } from "~/components/phone-mockup";
|
||||
import { SectionLabel } from "~/components/section-label";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { productClaims } from "~/content/product";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "How it works",
|
||||
description:
|
||||
"See how Medscribe turns visits and medication routines into one local record a person can review and carry.",
|
||||
};
|
||||
|
||||
const inputs = [
|
||||
{
|
||||
icon: Keyboard,
|
||||
title: "Type or paste",
|
||||
body: "Start from notes you already have and keep the source close to the result.",
|
||||
},
|
||||
{
|
||||
icon: Camera,
|
||||
title: "Import a screenshot",
|
||||
body: "Bring in a portal summary or prescription label, then correct extracted fields.",
|
||||
},
|
||||
{
|
||||
icon: FileText,
|
||||
title: "Attach a document",
|
||||
body: "Use a photo or document as the input to a local review workflow.",
|
||||
},
|
||||
{
|
||||
icon: Mic,
|
||||
title: "Record with consent",
|
||||
body: "Optional visit audio requires consent and is transcribed on the device.",
|
||||
},
|
||||
] as const;
|
||||
|
||||
const boundaries = [
|
||||
"It does not diagnose or recommend treatment.",
|
||||
"It does not calculate, shift, or supply medication timing.",
|
||||
"It does not replace a pharmacist, prescribing team, or emergency service.",
|
||||
"It does not automatically synchronize a household in the background.",
|
||||
] as const;
|
||||
|
||||
export default function HowItWorksPage() {
|
||||
return (
|
||||
<>
|
||||
<section className="px-4 pt-16 pb-18 sm:px-6 sm:pt-22">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<Badge variant="success">Current beta workflow</Badge>
|
||||
<h1 className="text-foreground mt-6 max-w-4xl font-serif text-5xl leading-[1.02] font-semibold tracking-[-0.03em] sm:text-6xl">
|
||||
One record, built through everyday use.
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-6 max-w-2xl text-lg leading-relaxed sm:text-xl">
|
||||
Medscribe is useful at a handoff only if ordinary moments keep the
|
||||
picture current. Every generated result has a person in the review
|
||||
loop.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="border-border-soft bg-surface/75 border-y px-4 py-18 sm:px-6 lg:py-22">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<SectionLabel>Start where the information is</SectionLabel>
|
||||
<h2 className="text-foreground mt-4 max-w-3xl font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||
Four ways in. One review checkpoint.
|
||||
</h2>
|
||||
<div className="mt-10 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{inputs.map((item, index) => {
|
||||
const Icon = item.icon;
|
||||
|
||||
return (
|
||||
<article
|
||||
key={item.title}
|
||||
className="border-border-soft bg-canvas rounded-2xl border p-6"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="bg-primary-light text-primary flex size-11 items-center justify-center rounded-xl">
|
||||
<Icon className="size-5" aria-hidden="true" />
|
||||
</span>
|
||||
<span className="text-muted-foreground text-xs font-bold">
|
||||
0{index + 1}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-foreground mt-5 font-serif text-2xl font-semibold">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="text-muted-foreground mt-3 text-sm leading-relaxed">
|
||||
{item.body}
|
||||
</p>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="border-primary/20 bg-primary-light/45 mt-6 flex gap-3 rounded-2xl border p-5">
|
||||
<Check
|
||||
className="text-primary mt-0.5 size-5 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<p className="text-foreground text-sm leading-relaxed">
|
||||
The person reviews summaries and extracted medication fields.
|
||||
Medscribe does not silently turn model output into an
|
||||
authoritative medical record.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="px-4 py-20 sm:px-6 lg:py-28">
|
||||
<div className="mx-auto max-w-6xl space-y-24">
|
||||
<div className="grid gap-12 lg:grid-cols-2 lg:items-center">
|
||||
<div>
|
||||
<SectionLabel>Review what changed</SectionLabel>
|
||||
<h2 className="text-foreground mt-4 font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||
A summary is a draft, not a verdict.
|
||||
</h2>
|
||||
<p className="text-muted-foreground mt-5 leading-relaxed">
|
||||
Supported transcription and summarization run locally. The visit
|
||||
remains available beside the summary so a person can check
|
||||
follow-ups and wording.
|
||||
</p>
|
||||
<p className="border-border-soft bg-surface text-muted-foreground mt-5 rounded-2xl border p-5 text-sm leading-relaxed">
|
||||
<strong className="text-foreground">What comes next:</strong> a
|
||||
structured review inbox for accepting, correcting, or dismissing
|
||||
changes inferred from a visit.
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-primary-light/45 justify-self-center rounded-[2.5rem] p-7">
|
||||
<PhoneMockup
|
||||
src="/screenshots/appointment-summary.png"
|
||||
alt="Synthetic Medscribe beta visit summary"
|
||||
width={235}
|
||||
/>
|
||||
<p className="text-muted-foreground mt-4 text-center text-xs">
|
||||
Synthetic demo data · beta interface
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-12 lg:grid-cols-2 lg:items-center">
|
||||
<div className="justify-self-center rounded-[2.5rem] bg-[#f4f0e8] p-7 lg:order-1 dark:bg-[#151b25]">
|
||||
<PhoneMockup
|
||||
src="/screenshots/medication-detail.png"
|
||||
alt="Synthetic Medscribe beta medication detail"
|
||||
width={235}
|
||||
/>
|
||||
<p className="text-muted-foreground mt-4 text-center text-xs">
|
||||
Synthetic demo data · beta interface
|
||||
</p>
|
||||
</div>
|
||||
<div className="lg:order-2">
|
||||
<SectionLabel>Maintain the medication picture</SectionLabel>
|
||||
<h2 className="text-foreground mt-4 font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||
Facts and schedules carry their boundary with them.
|
||||
</h2>
|
||||
<p className="text-muted-foreground mt-5 leading-relaxed">
|
||||
{productClaims.medicationFacts.publicCopy} Schedules can record
|
||||
whether they are fixed-time or as-needed, the times supplied by
|
||||
the person's source, confirmation, and timezone.
|
||||
</p>
|
||||
<p className="text-muted-foreground mt-5 text-sm leading-relaxed">
|
||||
Medscribe records timing supplied by a label, pharmacist, or
|
||||
prescribing team. It does not calculate or shift medication
|
||||
times.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-12 lg:grid-cols-2 lg:items-center">
|
||||
<div>
|
||||
<SectionLabel>Prepare the handoff</SectionLabel>
|
||||
<h2 className="text-foreground mt-4 font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||
Know Me is the view. Sharing is a separate choice.
|
||||
</h2>
|
||||
<p className="text-muted-foreground mt-5 leading-relaxed">
|
||||
Open the local Know Me screen when someone needs a concise
|
||||
picture. For a caregiver, pair devices, compare a safety number,
|
||||
review the record, and manually export an encrypted snapshot.
|
||||
</p>
|
||||
<p className="text-muted-foreground mt-5 text-sm leading-relaxed">
|
||||
The current import path does not yet require the sender to be in
|
||||
the recipient's active trust list, so recipients must
|
||||
verify the sender out of band. Revocation prevents a future
|
||||
local export; it cannot invalidate already-exported ciphertext
|
||||
or erase a copy the recipient imported.
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-primary-light/45 justify-self-center rounded-[2.5rem] p-7">
|
||||
<PhoneMockup
|
||||
src="/screenshots/emergency-record.png"
|
||||
alt="Synthetic Medscribe beta Know Me handoff"
|
||||
width={235}
|
||||
/>
|
||||
<p className="text-muted-foreground mt-4 text-center text-xs">
|
||||
Synthetic demo data · beta interface
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="border-border-soft border-t bg-[#f4f0e8]/75 px-4 py-20 sm:px-6 dark:bg-[#151b25]">
|
||||
<div className="mx-auto grid max-w-6xl gap-12 lg:grid-cols-2">
|
||||
<div>
|
||||
<div className="bg-primary-light text-primary flex size-12 items-center justify-center rounded-xl">
|
||||
<ShieldAlert className="size-6" aria-hidden="true" />
|
||||
</div>
|
||||
<h2 className="text-foreground mt-5 font-serif text-4xl font-semibold tracking-tight">
|
||||
What Medscribe does not do
|
||||
</h2>
|
||||
<p className="text-muted-foreground mt-4 leading-relaxed">
|
||||
A useful boundary is part of the product, not fine print after it.
|
||||
</p>
|
||||
</div>
|
||||
<ul className="space-y-3">
|
||||
{boundaries.map((item) => (
|
||||
<li
|
||||
key={item}
|
||||
className="border-border-soft bg-surface text-foreground flex gap-3 rounded-2xl border p-4 text-sm leading-relaxed"
|
||||
>
|
||||
<Check
|
||||
className="text-primary mt-0.5 size-5 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="px-4 py-20 sm:px-6">
|
||||
<div className="mx-auto flex max-w-6xl flex-col items-start justify-between gap-7 rounded-[2rem] bg-[#0f4f4b] px-7 py-10 text-white sm:px-10 md:flex-row md:items-center">
|
||||
<div>
|
||||
<h2 className="font-serif text-3xl font-semibold">
|
||||
See the trust boundary task by task.
|
||||
</h2>
|
||||
<p className="mt-2 text-sm leading-relaxed text-teal-50/75">
|
||||
Read what stays local, what can use a network, and what remains
|
||||
unfinished.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="border-white/20 bg-white text-[#0f4f4b] hover:bg-teal-50"
|
||||
>
|
||||
<Link href="/privacy">
|
||||
Privacy & evidence
|
||||
<ArrowRight className="size-4" aria-hidden="true" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -14,6 +14,7 @@ export const metadata: Metadata = {
|
||||
title: "Solution Landscape",
|
||||
description:
|
||||
"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 =
|
||||
|
||||
@@ -7,9 +7,15 @@ export default function MarketingLayout({
|
||||
}: Readonly<{ children: React.ReactNode }>) {
|
||||
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 />
|
||||
<SiteHeader />
|
||||
<main>{children}</main>
|
||||
<main id="main-content">{children}</main>
|
||||
<SiteFooter />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,332 +1,522 @@
|
||||
import type { Metadata } from "next";
|
||||
import {
|
||||
AlertTriangle,
|
||||
ChevronRight,
|
||||
ClipboardList,
|
||||
HeartPulse,
|
||||
Lock,
|
||||
Mic,
|
||||
Pill,
|
||||
Shield,
|
||||
Users,
|
||||
ArrowRight,
|
||||
Check,
|
||||
ContactRound,
|
||||
KeyRound,
|
||||
LockKeyhole,
|
||||
ScanLine,
|
||||
ShieldCheck,
|
||||
Smartphone,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { FeatureShowcase } from "~/components/feature-showcase";
|
||||
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 { ProductDevice } from "~/components/product-device";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "~/components/ui/card";
|
||||
import { productClaims, productRelease } from "~/content/product";
|
||||
import { BETA_INVITE_MAILTO } from "~/lib/beta";
|
||||
|
||||
const quickActions = [
|
||||
{
|
||||
icon: HeartPulse,
|
||||
title: "Know Me card",
|
||||
meta: "Baseline, conditions, allergies",
|
||||
featured: true,
|
||||
},
|
||||
{
|
||||
icon: Pill,
|
||||
title: "Time-critical meds",
|
||||
meta: "Schedules caregivers can trust",
|
||||
},
|
||||
{
|
||||
icon: Mic,
|
||||
title: "Visit capture",
|
||||
meta: "Consent-gated, on-device",
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: "Caregiver handoff",
|
||||
meta: "One current shared truth",
|
||||
},
|
||||
];
|
||||
export const metadata: Metadata = {
|
||||
title: "Carry the context when care gets complicated",
|
||||
description:
|
||||
"Medscribe is a private, patient-controlled beta that helps keep visits, medications, and critical context together on your device.",
|
||||
};
|
||||
|
||||
const pillars = [
|
||||
const visitMoments = [
|
||||
{
|
||||
icon: ClipboardList,
|
||||
title: "Keep the record current",
|
||||
body: "Capture visits, scan labels, and keep conditions, medications, allergies, contacts, and caregiver notes in one portable record.",
|
||||
number: "01",
|
||||
eyebrow: "Visits stay findable",
|
||||
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,
|
||||
title: "Make the critical context obvious",
|
||||
body: "The emergency view prioritizes the facts clinicians need fast: baseline, time-critical meds, active conditions, allergies, and who to call.",
|
||||
number: "02",
|
||||
eyebrow: "Words stay checkable",
|
||||
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,
|
||||
title: "Stay private by architecture",
|
||||
body: "Transcription, OCR, and medication chat run locally. No cloud AI processing. Sharing should be explicit, revocable, and encrypted.",
|
||||
number: "03",
|
||||
eyebrow: "The next step stays visible",
|
||||
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",
|
||||
body: "When a caregiver is overwhelmed, the important facts are already assembled: conditions, medications, allergies, baseline, contacts, and recent changes.",
|
||||
value: productRelease.drugFacts.toLocaleString("en-US"),
|
||||
label: "bundled medication records",
|
||||
},
|
||||
{
|
||||
title: "Safer medication moments",
|
||||
body: "Time-critical schedules and dose history stay visible, so a missed or delayed medication is easier to catch before it becomes a crisis.",
|
||||
value: productRelease.rxNormTerms.toLocaleString("en-US"),
|
||||
label: "RxNorm terms for matching",
|
||||
},
|
||||
{
|
||||
title: "More confident families",
|
||||
body: "Caregivers can see what changed, where it came from, and what still needs a clinician or pharmacist instead of guessing from memory.",
|
||||
value: productRelease.interactionPairs.toLocaleString("en-US"),
|
||||
label: "known interaction pairs",
|
||||
},
|
||||
];
|
||||
] as const;
|
||||
|
||||
const privacyPrinciples = [
|
||||
{
|
||||
icon: Smartphone,
|
||||
title: "Your working record stays local",
|
||||
body: productClaims.localRecord.publicCopy,
|
||||
},
|
||||
{
|
||||
icon: LockKeyhole,
|
||||
title: "AI runs on the device",
|
||||
body: productClaims.localAi.publicCopy,
|
||||
},
|
||||
{
|
||||
icon: ShieldCheck,
|
||||
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() {
|
||||
const today = new Date().toLocaleDateString("en-US", {
|
||||
weekday: "long",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-6xl space-y-14 px-4 py-8 sm:px-6 sm:py-12 lg:space-y-20 lg:py-14">
|
||||
|
||||
{/* Hero */}
|
||||
<section className="grid items-center gap-8 lg:grid-cols-[minmax(0,1fr)_minmax(230px,280px)] lg:gap-14">
|
||||
<div className="space-y-6">
|
||||
<div className="animate-fade-in-up space-y-5">
|
||||
<SectionLabel>Medscribe · Private complex-care context</SectionLabel>
|
||||
<h1 className="max-w-2xl font-serif text-4xl leading-[1.05] font-semibold tracking-tight text-foreground sm:text-5xl lg:text-6xl">
|
||||
Be known when care gets complicated
|
||||
</h1>
|
||||
<p className="max-w-xl text-base leading-relaxed text-muted-foreground sm:text-lg">
|
||||
A private medical context app for families managing complex care.
|
||||
It keeps visits, medications, baseline, contacts, allergies, and
|
||||
emergency facts ready before the chart catches up.
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">{today}</p>
|
||||
<>
|
||||
<section className="relative isolate min-h-[calc(100svh-4rem)] overflow-hidden px-4 pt-14 sm:px-6 sm:pt-20 lg:pt-24">
|
||||
<div className="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" />
|
||||
<div className="mx-auto max-w-7xl text-center">
|
||||
<div className="animate-fade-in-up flex items-center justify-center gap-3">
|
||||
<span className="bg-success-light text-success rounded-full px-3 py-1.5 text-xs font-bold tracking-[0.12em] uppercase">
|
||||
{productRelease.stage}
|
||||
</span>
|
||||
<span className="text-muted-foreground text-xs font-semibold tracking-[0.12em] uppercase">
|
||||
{productRelease.platforms}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="animate-fade-in-up-delay-1 flex flex-wrap gap-3">
|
||||
<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]">
|
||||
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 them—kept
|
||||
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">
|
||||
<a href="/executive-summary">
|
||||
Read executive summary
|
||||
<ChevronRight className="size-4" />
|
||||
</a>
|
||||
<Link href="/beta">
|
||||
Request beta access
|
||||
<ArrowRight className="size-4" aria-hidden="true" />
|
||||
</Link>
|
||||
</Button>
|
||||
<Button asChild variant="outline" size="lg">
|
||||
<a href="#features">See how it works</a>
|
||||
<Button asChild size="lg" variant="outline">
|
||||
<Link href="/how-it-works">See how it works</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="animate-fade-in-up-delay-2 flex flex-wrap gap-2">
|
||||
<Badge variant="success">Beta available</Badge>
|
||||
<Badge variant="outline">iOS · TestFlight</Badge>
|
||||
<Badge variant="outline">Android · Play internal</Badge>
|
||||
<Badge variant="outline">Emergency-ready</Badge>
|
||||
<Badge variant="outline">No cloud AI</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="animate-fade-in-up-delay-3 flex justify-center lg:justify-end">
|
||||
<div className="flex w-fit items-start justify-center">
|
||||
<PhoneMockup
|
||||
src="/screenshots/home.png"
|
||||
alt="Medscribe home dashboard"
|
||||
<div className="relative mx-auto mt-12 h-[36rem] max-w-5xl sm:h-[46rem] lg:mt-8 lg:h-[54rem]">
|
||||
<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]">
|
||||
<ProductDevice
|
||||
src="/screenshots/pitch/summary-natural.webp"
|
||||
alt="Medscribe visit summary in a natural titanium iPhone product bezel"
|
||||
sizes="(min-width: 1024px) 416px, (min-width: 640px) 352px, 272px"
|
||||
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't.
|
||||
</h2>
|
||||
<p className="text-muted-foreground mx-auto mt-8 max-w-2xl text-lg leading-relaxed sm:text-xl">
|
||||
One instruction lives in a portal. A medication label sits in a
|
||||
cabinet. The latest change lives in someone's memory. Medscribe
|
||||
is the patient-held thread between those moments.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<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 view—not scattered across paper and memory.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-16 grid gap-14 lg:grid-cols-2 lg:items-center lg:gap-6">
|
||||
<div className="relative mx-auto h-[42rem] w-full max-w-2xl sm:h-[50rem] lg:h-[56rem]">
|
||||
<div className="absolute top-20 left-[4%] w-[17rem] -rotate-6 sm:w-[21rem] lg:w-[23rem]">
|
||||
<ProductDevice
|
||||
src="/screenshots/pitch/medication-detail-natural.webp"
|
||||
alt="Medscribe medication detail in a natural titanium iPhone product bezel"
|
||||
sizes="(min-width: 1024px) 368px, (min-width: 640px) 336px, 272px"
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute top-0 right-[2%] z-10 w-[18rem] rotate-5 sm:w-[22rem] lg:w-[24rem]">
|
||||
<ProductDevice
|
||||
src="/screenshots/pitch/medications-black.webp"
|
||||
alt="Medscribe medication list in a black titanium iPhone product bezel"
|
||||
sizes="(min-width: 1024px) 384px, (min-width: 640px) 352px, 288px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-px overflow-hidden rounded-[2rem] border border-white/12 bg-white/12">
|
||||
{[
|
||||
{
|
||||
title: "Show where a schedule came from",
|
||||
body: "Schedules can carry their source, confirmation, timezone, and freshness state.",
|
||||
},
|
||||
{
|
||||
title: "Keep facts deterministic",
|
||||
body: productClaims.medicationFacts.publicCopy,
|
||||
},
|
||||
{
|
||||
title: "Make uncertainty visible",
|
||||
body: "Missing or stale details stay visible so the person or clinician can verify them.",
|
||||
},
|
||||
].map((item) => (
|
||||
<article
|
||||
key={item.title}
|
||||
className="bg-[#0b3b37]/90 p-7 sm:p-9"
|
||||
>
|
||||
<Check className="size-5 text-teal-200" aria-hidden="true" />
|
||||
<h3 className="mt-5 font-serif text-2xl font-semibold sm:text-3xl">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="mt-3 leading-relaxed text-teal-50/70">
|
||||
{item.body}
|
||||
</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="pitch-lazy bg-[#f0ece4] px-4 py-24 sm:px-6 lg:py-36 dark:bg-[#111827]">
|
||||
<div className="mx-auto grid max-w-7xl items-center gap-14 lg:grid-cols-[1fr_0.9fr] lg:gap-24">
|
||||
<div className="order-2 lg:order-1">
|
||||
<p className="text-primary text-xs font-bold tracking-[0.2em] uppercase">
|
||||
Ask from the record
|
||||
</p>
|
||||
<h2 className="text-foreground mt-6 max-w-2xl font-serif text-[clamp(3.25rem,6vw,6.5rem)] leading-[0.94] font-semibold tracking-[-0.045em]">
|
||||
Better questions begin with your context.
|
||||
</h2>
|
||||
<p className="text-muted-foreground mt-8 max-w-xl text-lg leading-relaxed sm:text-xl">
|
||||
Ask about a visit or medication without uploading the record to a
|
||||
remote model. Answers are grounded in local history and bundled
|
||||
facts, with the boundary kept in view.
|
||||
</p>
|
||||
<p className="border-primary/35 text-muted-foreground mt-7 max-w-xl border-l-2 pl-5 text-sm leading-relaxed">
|
||||
Informational only. Medication changes still belong with a
|
||||
clinician or pharmacist.
|
||||
</p>
|
||||
</div>
|
||||
<div className="order-1 mx-auto w-[min(78vw,27rem)] lg:order-2">
|
||||
<ProductDevice
|
||||
src="/screenshots/pitch/ask-black.webp"
|
||||
alt="Medscribe Ask response grounded in local health context, shown in a black titanium iPhone product bezel"
|
||||
sizes="(min-width: 1024px) 432px, (min-width: 640px) 384px, 78vw"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Strategic spine */}
|
||||
<section className="animate-fade-in-up space-y-5">
|
||||
<SectionLabel>The story</SectionLabel>
|
||||
<div className="grid gap-6 lg:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)] lg:items-start">
|
||||
<div className="space-y-3">
|
||||
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
||||
The story scatters right when it matters
|
||||
</h2>
|
||||
<p className="max-w-xl leading-relaxed text-muted-foreground">
|
||||
A daughter remembers the medication schedule. A spouse knows what
|
||||
normal looks like. A specialist mentioned a change three visits ago.
|
||||
In a crisis, those details are scattered across people, portals, and
|
||||
memory.
|
||||
</p>
|
||||
<p className="max-w-xl text-sm leading-relaxed text-muted-foreground/80 italic">
|
||||
I built the first version of Medscribe after watching my own family
|
||||
try to hold this together for my grandfather. The record scattered
|
||||
right when we needed it most.
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border-soft bg-surface p-5 shadow-[0_1px_4px_rgba(0,0,0,0.05)]">
|
||||
<div className="flex items-start gap-3">
|
||||
<HeartPulse className="mt-0.5 size-5 shrink-0 text-primary" />
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-serif text-xl font-semibold tracking-tight">
|
||||
One current story
|
||||
</h3>
|
||||
<p className="text-sm leading-relaxed text-muted-foreground">
|
||||
Medscribe gives the family a private place to keep that context
|
||||
alive: current meds, timing, baseline, allergies, recent visit
|
||||
summaries, emergency contacts, and the source trail behind each
|
||||
fact.
|
||||
</p>
|
||||
<section className="pitch-lazy overflow-hidden px-4 py-24 sm:px-6 lg:py-36">
|
||||
<div className="mx-auto max-w-7xl">
|
||||
<div className="grid items-center gap-16 lg:grid-cols-[0.92fr_1.08fr] lg:gap-24">
|
||||
<div className="relative mx-auto w-[min(80vw,28rem)]">
|
||||
<div className="bg-primary-light/70 absolute inset-[12%_-12%_8%] -z-10 rounded-[4rem] blur-2xl" />
|
||||
<ProductDevice
|
||||
src="/screenshots/pitch/know-me-natural.webp"
|
||||
alt="Medscribe Know Me handoff record in a natural titanium iPhone product bezel"
|
||||
sizes="(min-width: 1024px) 448px, (min-width: 640px) 400px, 80vw"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-primary text-xs font-bold tracking-[0.2em] uppercase">
|
||||
At the handoff
|
||||
</p>
|
||||
<h2 className="text-foreground mt-6 max-w-2xl font-serif text-[clamp(3.2rem,6vw,6.2rem)] leading-[0.95] font-semibold tracking-[-0.045em]">
|
||||
Bring the picture when the chart is incomplete.
|
||||
</h2>
|
||||
<p className="text-muted-foreground mt-8 max-w-xl text-lg leading-relaxed sm:text-xl">
|
||||
Know Me gathers selected medications, allergies, conditions,
|
||||
baseline, and contacts into one readable snapshot. Freshness
|
||||
signals show what may need another look.
|
||||
</p>
|
||||
<div className="mt-9 grid gap-4 sm:grid-cols-2">
|
||||
<div className="border-border-soft bg-surface rounded-2xl border p-5">
|
||||
<ScanLine
|
||||
className="text-primary size-6"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<p className="text-foreground mt-4 font-semibold">
|
||||
Quick handoff
|
||||
</p>
|
||||
<p className="text-muted-foreground mt-2 text-sm leading-relaxed">
|
||||
Show or share a selected, readable snapshot.
|
||||
</p>
|
||||
</div>
|
||||
<div className="border-border-soft bg-surface rounded-2xl border p-5">
|
||||
<ContactRound
|
||||
className="text-primary size-6"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<p className="text-foreground mt-4 font-semibold">
|
||||
Caregiver copy
|
||||
</p>
|
||||
<p className="text-muted-foreground mt-2 text-sm leading-relaxed">
|
||||
Manually export an encrypted copy for a paired recipient.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{quickActions.map((action) => {
|
||||
const Icon = action.icon;
|
||||
|
||||
return (
|
||||
<Card
|
||||
key={action.title}
|
||||
className={`card-hover ${action.featured ? "border-primary-button/20 bg-primary-button text-white" : ""}`}
|
||||
>
|
||||
<CardHeader className="gap-3">
|
||||
<div
|
||||
className={`flex size-10 items-center justify-center rounded-[10px] ${
|
||||
action.featured ? "bg-white/18" : "bg-primary-light"
|
||||
}`}
|
||||
<div className="mt-24 overflow-hidden rounded-[2.5rem] bg-[#0f4f4b] p-7 text-white sm:p-10 lg:p-14">
|
||||
<div className="grid gap-12 lg:grid-cols-[0.85fr_1.15fr] lg:items-center">
|
||||
<div>
|
||||
<KeyRound className="size-7 text-teal-200" aria-hidden="true" />
|
||||
<h3 className="mt-5 font-serif text-4xl leading-tight font-semibold sm:text-5xl">
|
||||
Share a snapshot. Not an invisible feed.
|
||||
</h3>
|
||||
<p className="mt-5 max-w-xl leading-relaxed text-teal-50/75">
|
||||
Pair by QR code, compare a safety number, and choose when to
|
||||
export. The caregiver imports a local copy on their device.
|
||||
</p>
|
||||
</div>
|
||||
<ol className="grid gap-3 sm:grid-cols-3">
|
||||
{[
|
||||
["1", "You review", "Choose the record to send"],
|
||||
["2", "Encrypted copy", "Protected for the recipient key"],
|
||||
["3", "They import", "A local, readable snapshot"],
|
||||
].map(([number, title, body]) => (
|
||||
<li
|
||||
key={number}
|
||||
className="rounded-2xl border border-white/15 bg-white/8 p-5"
|
||||
>
|
||||
<Icon
|
||||
className={`size-5 ${action.featured ? "text-white" : "text-primary"}`}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className={action.featured ? "text-white" : ""}>
|
||||
{action.title}
|
||||
</CardTitle>
|
||||
<CardDescription
|
||||
className={action.featured ? "text-white/72" : ""}
|
||||
>
|
||||
{action.meta}
|
||||
</CardDescription>
|
||||
</div>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Interactive feature showcase */}
|
||||
<section id="features" className="animate-fade-in-up scroll-mt-24 space-y-5">
|
||||
<div className="space-y-2">
|
||||
<SectionLabel>The record</SectionLabel>
|
||||
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
||||
The pieces that keep the story current
|
||||
</h2>
|
||||
<p className="max-w-2xl text-muted-foreground">
|
||||
Visits, medications, reminders, search, and questions all point toward
|
||||
the same outcome: a record the family can trust when the stakes rise.
|
||||
</p>
|
||||
</div>
|
||||
<FeatureShowcase />
|
||||
</section>
|
||||
|
||||
{/* Privacy */}
|
||||
<section id="privacy" className="animate-fade-in-up scroll-mt-24 space-y-6">
|
||||
<div className="space-y-3">
|
||||
<SectionLabel>Why this is different</SectionLabel>
|
||||
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
||||
Built for the family record, not another cloud inbox
|
||||
</h2>
|
||||
<p className="max-w-2xl text-muted-foreground">
|
||||
The crowded space already has visit recorders, medication apps, and
|
||||
caregiver portals. Medscribe's wedge is the private, portable
|
||||
context layer families control before an institution is ready — and
|
||||
it works alongside the systems a care team already uses, not instead
|
||||
of them.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
{pillars.map((pillar) => {
|
||||
const Icon = pillar.icon;
|
||||
|
||||
return (
|
||||
<Card key={pillar.title} className="card-hover">
|
||||
<CardHeader>
|
||||
<div className="flex size-10 items-center justify-center rounded-[10px] bg-primary-light">
|
||||
<Icon className="size-5 text-primary" />
|
||||
</div>
|
||||
<CardTitle>{pillar.title}</CardTitle>
|
||||
<CardDescription>{pillar.body}</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<Card className="border-primary/15 bg-primary-light/40">
|
||||
<CardContent className="flex items-start gap-4 pt-5">
|
||||
<Lock className="mt-0.5 size-5 shrink-0 text-primary" />
|
||||
<p className="text-sm leading-relaxed text-foreground">
|
||||
All AI models run on your device. No health data is uploaded for
|
||||
processing. The medication chatbot stays in drug-information
|
||||
territory. It defers dosing and diagnosis questions to your
|
||||
clinician or pharmacist.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
{/* Impact */}
|
||||
<section id="impact" className="animate-fade-in-up scroll-mt-24 space-y-6">
|
||||
<div className="space-y-3">
|
||||
<SectionLabel>Potential impact</SectionLabel>
|
||||
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
||||
Less panic, more context, better handoffs
|
||||
</h2>
|
||||
<p className="max-w-2xl text-muted-foreground">
|
||||
The goal is simple: make the person easier to understand at the exact
|
||||
moment their care team has the least context.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
{impact.map((item) => (
|
||||
<Card key={item.title} className="card-hover">
|
||||
<CardHeader>
|
||||
<CardTitle>{item.title}</CardTitle>
|
||||
<CardDescription>{item.body}</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA */}
|
||||
<section className="animate-fade-in-up">
|
||||
<Card className="overflow-hidden border-primary-button/20 bg-primary-button text-white">
|
||||
<CardHeader className="gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="space-y-2">
|
||||
<CardTitle className="text-white">{BETA_CTA_TITLE}</CardTitle>
|
||||
<CardDescription className="text-white/75">
|
||||
{BETA_CTA_BODY}
|
||||
</CardDescription>
|
||||
<span className="flex size-8 items-center justify-center rounded-full bg-white/12 text-xs font-bold">
|
||||
{number}
|
||||
</span>
|
||||
<p className="mt-8 font-serif text-xl font-semibold">
|
||||
{title}
|
||||
</p>
|
||||
<p className="mt-2 text-sm leading-relaxed text-teal-50/65">
|
||||
{body}
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="border-white/30 bg-white text-primary-button hover:bg-white/90"
|
||||
>
|
||||
<a href={BETA_INVITE_MAILTO}>Request an invite</a>
|
||||
</Button>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
<p className="mt-8 border-t border-white/12 pt-6 text-xs leading-relaxed text-teal-50/60">
|
||||
Manual today—not live sync or a household cloud account.
|
||||
Import-side sender trust enforcement is still being hardened;
|
||||
revocation cannot recall a copy already exported.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</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 resets—not 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,388 @@
|
||||
import type { Metadata } from "next";
|
||||
import {
|
||||
AlertTriangle,
|
||||
ArrowRight,
|
||||
Check,
|
||||
Database,
|
||||
FileCheck2,
|
||||
KeyRound,
|
||||
LockKeyhole,
|
||||
Wifi,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { SectionLabel } from "~/components/section-label";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { productClaims, productRelease } from "~/content/product";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Privacy & evidence",
|
||||
description:
|
||||
"A task-by-task view of Medscribe's local architecture, network behavior, caregiver sharing, evidence, and beta limitations.",
|
||||
};
|
||||
|
||||
const dataMap = [
|
||||
{
|
||||
task: "Visit transcription",
|
||||
location: "Device",
|
||||
network: "No cloud inference",
|
||||
control: "Recording requires consent",
|
||||
},
|
||||
{
|
||||
task: "Summary generation",
|
||||
location: "Device",
|
||||
network: "No cloud inference",
|
||||
control: "The person reviews output",
|
||||
},
|
||||
{
|
||||
task: "Medication-label OCR",
|
||||
location: "Device",
|
||||
network: "No cloud inference",
|
||||
control: "Extracted fields remain editable",
|
||||
},
|
||||
{
|
||||
task: "Drug facts and known pairs",
|
||||
location: "Bundled on device",
|
||||
network: "No runtime fact lookup",
|
||||
control: "Sources and coverage are disclosed",
|
||||
},
|
||||
{
|
||||
task: "Model installation or update",
|
||||
location: "Downloaded to device",
|
||||
network: "Network may be required",
|
||||
control: "Approved URLs and file integrity checks",
|
||||
},
|
||||
{
|
||||
task: "Caregiver sharing",
|
||||
location: "Encrypted device-to-device snapshot",
|
||||
network: "The person chooses ciphertext transport",
|
||||
control: "Verify out of band; import trust enforcement is incomplete",
|
||||
},
|
||||
{
|
||||
task: "Primary record storage",
|
||||
location: "Local app database",
|
||||
network: "No Medscribe cloud account",
|
||||
control: "The unlocked device remains a trust boundary",
|
||||
},
|
||||
{
|
||||
task: "Website analytics",
|
||||
location: "Marketing site only",
|
||||
network: "Optional Umami script when configured",
|
||||
control: "Disabled when no website ID is configured",
|
||||
},
|
||||
] as const;
|
||||
|
||||
const protects = [
|
||||
"Runtime disclosure of visits or medications to a cloud AI provider",
|
||||
"A Medscribe account database becoming the default copy of the record",
|
||||
"A readable caregiver export being sent to someone who was not paired",
|
||||
] as const;
|
||||
|
||||
const doesNotProtect = [
|
||||
"Information viewed on an unlocked or compromised phone",
|
||||
"A snapshot that a verified recipient already imported",
|
||||
"Someone intentionally copying or sharing visible information",
|
||||
"Every possible drug interaction or model failure",
|
||||
] as const;
|
||||
|
||||
export default function PrivacyPage() {
|
||||
return (
|
||||
<>
|
||||
<section className="px-4 pt-16 pb-18 sm:px-6 sm:pt-22">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Badge variant="success">
|
||||
Last verified {productRelease.verifiedOn}
|
||||
</Badge>
|
||||
<Badge variant="outline">Internal beta</Badge>
|
||||
</div>
|
||||
<h1 className="text-foreground mt-6 max-w-4xl font-serif text-5xl leading-[1.02] font-semibold tracking-[-0.03em] sm:text-6xl">
|
||||
Privacy should be specific enough to challenge.
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-6 max-w-3xl text-lg leading-relaxed sm:text-xl">
|
||||
“Local” is not a magic word. Here is where each task happens, when a
|
||||
network can be involved, what the design protects, and what it does
|
||||
not.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="border-border-soft bg-surface/80 border-y px-4 py-18 sm:px-6 lg:py-22">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<SectionLabel>Data map</SectionLabel>
|
||||
<h2 className="text-foreground mt-4 font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||
Task by task, not “everything” absolutes.
|
||||
</h2>
|
||||
|
||||
<div className="border-border-soft bg-canvas mt-10 overflow-x-auto rounded-2xl border shadow-[0_18px_55px_rgba(15,23,42,0.06)]">
|
||||
<table className="w-full min-w-[780px] border-collapse text-left text-sm">
|
||||
<caption className="sr-only">
|
||||
Medscribe tasks, processing location, network behavior, and user
|
||||
control
|
||||
</caption>
|
||||
<thead>
|
||||
<tr className="border-border-soft bg-surface-soft border-b">
|
||||
{[
|
||||
"Task",
|
||||
"Where it happens",
|
||||
"Network behavior",
|
||||
"User control",
|
||||
].map((heading) => (
|
||||
<th
|
||||
key={heading}
|
||||
scope="col"
|
||||
className="text-muted-foreground px-5 py-4 text-xs font-bold tracking-[0.1em] uppercase"
|
||||
>
|
||||
{heading}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataMap.map((row) => (
|
||||
<tr
|
||||
key={row.task}
|
||||
className="border-border-soft/80 border-b last:border-0"
|
||||
>
|
||||
<th
|
||||
scope="row"
|
||||
className="text-foreground px-5 py-4 font-semibold"
|
||||
>
|
||||
{row.task}
|
||||
</th>
|
||||
<td className="text-muted-foreground px-5 py-4">
|
||||
{row.location}
|
||||
</td>
|
||||
<td className="text-muted-foreground px-5 py-4">
|
||||
{row.network}
|
||||
</td>
|
||||
<td className="text-muted-foreground px-5 py-4">
|
||||
{row.control}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="px-4 py-20 sm:px-6 lg:py-28">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<div className="grid gap-6 lg:grid-cols-3">
|
||||
{[
|
||||
{
|
||||
icon: LockKeyhole,
|
||||
title: "Supported AI work stays local",
|
||||
body: productClaims.localAi.publicCopy,
|
||||
},
|
||||
{
|
||||
icon: Database,
|
||||
title: "The primary record stays local",
|
||||
body: `${productClaims.localRecord.publicCopy} The beta relies on the device and operating system's protections; it does not claim a separately encrypted app database.`,
|
||||
},
|
||||
{
|
||||
icon: Wifi,
|
||||
title: "Downloads are the network exception",
|
||||
body: "Installing or updating a model can require a network. Approved model files are checked against an expected SHA-256 digest before use.",
|
||||
},
|
||||
].map((item) => {
|
||||
const Icon = item.icon;
|
||||
|
||||
return (
|
||||
<article
|
||||
key={item.title}
|
||||
className="border-border-soft bg-surface rounded-2xl border p-6"
|
||||
>
|
||||
<span className="bg-primary-light text-primary flex size-11 items-center justify-center rounded-xl">
|
||||
<Icon className="size-5" aria-hidden="true" />
|
||||
</span>
|
||||
<h3 className="text-foreground mt-5 font-serif text-2xl font-semibold">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="text-muted-foreground mt-3 text-sm leading-relaxed">
|
||||
{item.body}
|
||||
</p>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="border-border-soft border-y bg-[#f4f0e8]/75 px-4 py-20 sm:px-6 lg:py-24 dark:bg-[#151b25]">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<div className="grid gap-12 lg:grid-cols-2">
|
||||
<div>
|
||||
<div className="bg-primary-light text-primary flex size-12 items-center justify-center rounded-xl">
|
||||
<KeyRound className="size-6" aria-hidden="true" />
|
||||
</div>
|
||||
<SectionLabel className="mt-6">Caregiver sharing</SectionLabel>
|
||||
<h2 className="text-foreground mt-4 font-serif text-4xl font-semibold tracking-tight">
|
||||
Verify a person, then export deliberately.
|
||||
</h2>
|
||||
<p className="text-muted-foreground mt-5 leading-relaxed">
|
||||
New snapshots use authenticated encryption targeted to the
|
||||
recipient key. Ciphertext integrity and wrong-recipient
|
||||
rejection are tested. The current import path does not yet
|
||||
require the sender to be in the recipient's active trust
|
||||
list, and recipient-key metadata is not authenticated and
|
||||
checked on import. Verify the sender out of band. The beta does
|
||||
not claim the Signal protocol or live synchronization.
|
||||
</p>
|
||||
</div>
|
||||
<div className="border-border-soft bg-surface rounded-2xl border p-6 sm:p-8">
|
||||
<p className="text-foreground font-semibold">
|
||||
The practical rule
|
||||
</p>
|
||||
<ol className="mt-5 space-y-4">
|
||||
{[
|
||||
"Compare the safety number with the intended caregiver.",
|
||||
"Review the snapshot before every material export.",
|
||||
"Re-export after important changes; it is not automatic.",
|
||||
"Treat revocation as a block on future local exports; it cannot invalidate already-exported ciphertext.",
|
||||
].map((item, index) => (
|
||||
<li
|
||||
key={item}
|
||||
className="text-muted-foreground flex gap-3 text-sm leading-relaxed"
|
||||
>
|
||||
<span className="bg-primary-light text-primary flex size-6 shrink-0 items-center justify-center rounded-full text-xs font-bold">
|
||||
{index + 1}
|
||||
</span>
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="px-4 py-20 sm:px-6 lg:py-28">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<SectionLabel>Evidence, not superlatives</SectionLabel>
|
||||
<div className="mt-4 grid gap-10 lg:grid-cols-[0.85fr_1.15fr] lg:items-end">
|
||||
<div>
|
||||
<h2 className="text-foreground font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||
What the current release can actually point to.
|
||||
</h2>
|
||||
</div>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
Medication language is based on named public reference datasets,
|
||||
but condensed wording is not FDA-reviewed. Coverage counts
|
||||
describe the bundle; they do not prove clinical completeness.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<dl className="mt-12 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{[
|
||||
[
|
||||
productRelease.drugFacts.toLocaleString(),
|
||||
"Condensed drug records",
|
||||
],
|
||||
[
|
||||
productRelease.rxNormTerms.toLocaleString(),
|
||||
"RxNorm search terms",
|
||||
],
|
||||
[
|
||||
productRelease.interactionPairs.toLocaleString(),
|
||||
"Known high-priority pairs",
|
||||
],
|
||||
[
|
||||
productRelease.flaggedForReview.toLocaleString(),
|
||||
"Records flagged for review",
|
||||
],
|
||||
].map(([value, label]) => (
|
||||
<div
|
||||
key={label}
|
||||
className="border-border-soft bg-surface rounded-2xl border p-6"
|
||||
>
|
||||
<dt className="text-muted-foreground text-sm leading-relaxed">
|
||||
{label}
|
||||
</dt>
|
||||
<dd className="text-foreground mt-2 font-serif text-4xl font-semibold">
|
||||
{value}
|
||||
</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
|
||||
<div className="border-border-soft bg-surface-soft mt-6 flex gap-3 rounded-2xl border p-5">
|
||||
<FileCheck2
|
||||
className="text-primary mt-0.5 size-5 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<p className="text-muted-foreground text-sm leading-relaxed">
|
||||
Source families include openFDA label data, RxNorm terminology,
|
||||
MED-RT and DailyMed class inputs, plus a bounded high-priority
|
||||
interaction list. Release dates and full methodology should be
|
||||
published with each wider release.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="border-border-soft bg-surface/75 border-t px-4 py-20 sm:px-6">
|
||||
<div className="mx-auto grid max-w-6xl gap-8 lg:grid-cols-2">
|
||||
<div className="border-success/25 bg-success-light/45 rounded-2xl border p-6 sm:p-8">
|
||||
<div className="flex items-center gap-3">
|
||||
<Check className="text-success size-6" aria-hidden="true" />
|
||||
<h2 className="text-foreground font-serif text-2xl font-semibold">
|
||||
Designed to protect against
|
||||
</h2>
|
||||
</div>
|
||||
<ul className="text-muted-foreground mt-5 space-y-3 text-sm leading-relaxed">
|
||||
{protects.map((item) => (
|
||||
<li key={item} className="border-success/30 border-l-2 pl-4">
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="rounded-2xl border border-amber-500/25 bg-amber-50 p-6 text-slate-900 sm:p-8 dark:bg-amber-950/30 dark:text-slate-100">
|
||||
<div className="flex items-center gap-3">
|
||||
<AlertTriangle
|
||||
className="size-6 text-amber-700 dark:text-amber-300"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<h2 className="font-serif text-2xl font-semibold">
|
||||
It does not eliminate
|
||||
</h2>
|
||||
</div>
|
||||
<ul className="mt-5 space-y-3 text-sm leading-relaxed text-slate-600 dark:text-slate-300">
|
||||
{doesNotProtect.map((item) => (
|
||||
<li key={item} className="border-l-2 border-amber-500/35 pl-4">
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto mt-10 flex max-w-6xl flex-col items-start justify-between gap-5 rounded-2xl bg-[#0f4f4b] p-7 text-white sm:flex-row sm:items-center sm:p-9">
|
||||
<div>
|
||||
<h2 className="font-serif text-3xl font-semibold">
|
||||
Help test the boundary.
|
||||
</h2>
|
||||
<p className="mt-2 text-sm text-teal-50/75">
|
||||
The internal beta is focused on correctness, comprehension, and
|
||||
caregiver handoffs.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="border-white/20 bg-white text-[#0f4f4b] hover:bg-teal-50"
|
||||
>
|
||||
<Link href="/beta">
|
||||
Beta expectations
|
||||
<ArrowRight className="size-4" aria-hidden="true" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { Metadata } from "next";
|
||||
|
||||
import { PrintButton } from "~/components/print-button";
|
||||
import { SectionLabel } from "~/components/section-label";
|
||||
import { WalgreensPrintSummary } from "~/components/walgreens-print-summary";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Walgreens executive summary",
|
||||
robots: { index: false, follow: false, nocache: true },
|
||||
};
|
||||
|
||||
export default function WalgreensSummaryPage() {
|
||||
return (
|
||||
<>
|
||||
<WalgreensPrintSummary />
|
||||
<div className="print-hidden mx-auto max-w-2xl px-4 py-16 text-center sm:px-6 lg:py-24">
|
||||
<SectionLabel>Executive summary · Walgreens</SectionLabel>
|
||||
<h1 className="text-foreground mt-4 font-serif text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||
The pharmacy executive summary
|
||||
</h1>
|
||||
<p className="text-muted-foreground mx-auto mt-5 max-w-xl leading-relaxed">
|
||||
This page is formatted as a two-page print document: what Medscribe
|
||||
can do, and how every capability pays off twice, for the patient and
|
||||
for Walgreens. Use Print PDF (or Cmd+P, then Save as PDF) to export
|
||||
it.
|
||||
</p>
|
||||
<div className="mt-8 flex justify-center">
|
||||
<PrintButton size="lg" />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -17,11 +17,12 @@ const playfair = Playfair_Display({
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
default: "Medscribe | Be known when care gets complicated",
|
||||
default: "Medscribe | Carry the context when care gets complicated",
|
||||
template: "Medscribe | %s",
|
||||
},
|
||||
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: {
|
||||
icon: [
|
||||
{ url: "/favicon.ico", sizes: "any" },
|
||||
@@ -29,12 +30,14 @@ export const metadata: Metadata = {
|
||||
{ url: "/favicon-32x32.png", sizes: "32x32", 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: {
|
||||
title: "Medscribe",
|
||||
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",
|
||||
},
|
||||
};
|
||||
@@ -43,7 +46,10 @@ export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{ children: React.ReactNode }>) {
|
||||
return (
|
||||
<html lang="en" className={`${instrumentSans.variable} ${playfair.variable}`}>
|
||||
<html
|
||||
lang="en"
|
||||
className={`${instrumentSans.variable} ${playfair.variable}`}
|
||||
>
|
||||
<body className="min-h-screen">
|
||||
{children}
|
||||
<UmamiAnalytics />
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { MetadataRoute } from "next";
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return {
|
||||
rules: {
|
||||
userAgent: "*",
|
||||
allow: "/",
|
||||
disallow: [
|
||||
"/api/",
|
||||
"/briefs/",
|
||||
"/cvs",
|
||||
"/executive-summary",
|
||||
"/landscape",
|
||||
"/login",
|
||||
"/walgreens",
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
import Image from "next/image";
|
||||
import {
|
||||
HeartPulse,
|
||||
Layers,
|
||||
Mic,
|
||||
MessageSquare,
|
||||
ScanLine,
|
||||
ShieldCheck,
|
||||
} from "lucide-react";
|
||||
|
||||
/**
|
||||
* Pharmacy-specific print summary (CVS-targeted, reusable for other pharmacies).
|
||||
* Two designed pages, built for the product rather than mirroring the Northwell
|
||||
* layout:
|
||||
* Page 1 - what Medscribe can do (capabilities, patient-facing power).
|
||||
* Page 2 - every capability pays off twice (patient value -> CVS value).
|
||||
* Reuses the `northwell-print-*` sheet/label classes from globals.css only as
|
||||
* the page frame; the interior is its own design.
|
||||
*/
|
||||
|
||||
function PrintPage({
|
||||
page,
|
||||
children,
|
||||
}: Readonly<{ page: number; children: React.ReactNode }>) {
|
||||
return (
|
||||
<article
|
||||
className={
|
||||
page === 1
|
||||
? "northwell-print-page"
|
||||
: "northwell-print-page northwell-print-page--continuation"
|
||||
}
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img className="northwell-print-wash" src="/print-wash.svg" alt="" />
|
||||
{page === 1 ? (
|
||||
<header className="northwell-print-header">
|
||||
<div className="flex items-center gap-2.5">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img className="h-6 w-auto" src="/medscribe-ms-logo.svg" alt="" />
|
||||
<span className="font-serif text-[17px] font-semibold text-slate-950">
|
||||
Medscribe
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-[9px] font-semibold tracking-[0.18em] text-slate-500 uppercase">
|
||||
Executive summary · CVS Health
|
||||
</span>
|
||||
</header>
|
||||
) : null}
|
||||
|
||||
<div className="northwell-print-content">{children}</div>
|
||||
|
||||
<footer className="northwell-print-footer">
|
||||
<span>Page {page} of 2</span>
|
||||
</footer>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
const capabilities = [
|
||||
{
|
||||
icon: Mic,
|
||||
title: "Understand a visit",
|
||||
body: "Type, paste, snap the after-visit summary, or record the conversation with consent. Medscribe transcribes and summarizes it on the phone.",
|
||||
},
|
||||
{
|
||||
icon: ScanLine,
|
||||
title: "Read a pill bottle",
|
||||
body: "Point the camera at a prescription label and the drug, dose, and schedule land in the record automatically.",
|
||||
},
|
||||
{
|
||||
icon: MessageSquare,
|
||||
title: "Answer the hard questions",
|
||||
body: "Ask whether two medications are safe together and get an answer grounded in bundled FDA drug facts and known interaction pairs, not a chatbot's guess.",
|
||||
},
|
||||
{
|
||||
icon: Layers,
|
||||
title: "Hold the whole picture",
|
||||
body: "Medications, allergies, conditions, baseline, and visit history across every provider, in one place the patient owns.",
|
||||
},
|
||||
{
|
||||
icon: HeartPulse,
|
||||
title: "Be ready for an emergency",
|
||||
body: "A patient-controlled Know Me card puts the time-critical facts one tap away for a caregiver or an ER.",
|
||||
},
|
||||
{
|
||||
icon: ShieldCheck,
|
||||
title: "Share without exposure",
|
||||
body: "End-to-end encrypted sharing lets a caregiver help, without a server ever reading the record.",
|
||||
},
|
||||
] as const;
|
||||
|
||||
const payoffs = [
|
||||
{
|
||||
patientTitle: "One picture, everywhere they are treated",
|
||||
patient:
|
||||
"Nothing falls through the cracks between a specialist, an urgent care, and the pharmacy.",
|
||||
cvsTitle: "One central pharmacy",
|
||||
cvs: "Refills route back to CVS and scattered scripts consolidate, instead of leaking to Amazon, Cost Plus, or mail order. Share of scripts, won.",
|
||||
},
|
||||
{
|
||||
patientTitle: "Medications that fit their day",
|
||||
patient:
|
||||
"Reminders and refill nudges that actually help, so they stay on their medications.",
|
||||
cvsTitle: "Star Ratings",
|
||||
cvs: "Diabetes, hypertension, and statin adherence are triple-weighted in Part D Star Ratings: Quality Bonus Payments and retention for Aetna and SilverScript.",
|
||||
},
|
||||
{
|
||||
patientTitle: "Fewer dangerous surprises",
|
||||
patient:
|
||||
"Interactions and duplicate prescriptions caught across every prescriber, not one pharmacy's slice.",
|
||||
cvsTitle: "Lower cost of care",
|
||||
cvs: "Fewer adverse events and ER visits: direct medical spend saved for the members shared with Caremark and Aetna.",
|
||||
},
|
||||
{
|
||||
patientTitle: "Ready at the counter",
|
||||
patient:
|
||||
"They arrive with a clean list and their routine questions already answered.",
|
||||
cvsTitle: "Pharmacist leverage",
|
||||
cvs: "Less low-value counter volume, more time for the billable MTM and CMR work pharmacists already do.",
|
||||
},
|
||||
{
|
||||
patientTitle: "A tool that is theirs",
|
||||
patient:
|
||||
"An advocate they control, on their own device. Not a sales pitch, and never on a server.",
|
||||
cvsTitle: "Engagement without the liability",
|
||||
cvs: "A CVS-branded habit patients actually open, with no PHI on a server: no new breach surface and no BAA to negotiate.",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export function CvsPrintSummary() {
|
||||
return (
|
||||
<div className="northwell-print-only" aria-hidden="true">
|
||||
<PrintPage page={1}>
|
||||
<div className="grid grid-cols-[1.16fr_0.84fr] items-start gap-9 pt-5">
|
||||
<div>
|
||||
<p className="northwell-print-label">What Medscribe is</p>
|
||||
<h1 className="mt-4 font-serif text-[40px] leading-[1.0] font-semibold tracking-[-0.035em] text-slate-950">
|
||||
It captures, understands, and carries the patient's whole
|
||||
medical picture.
|
||||
</h1>
|
||||
<p className="mt-5 text-[15px] leading-[1.55] text-slate-600">
|
||||
Real medical AI on the patient's phone. Medscribe summarizes
|
||||
visits, reads pill bottles, answers medication questions from
|
||||
bundled drug facts, and keeps one emergency-ready record across
|
||||
every provider. Nothing is sent to the cloud.
|
||||
</p>
|
||||
<div className="mt-6 grid grid-cols-2 gap-x-5 gap-y-2 text-[8.5px] font-bold tracking-[0.08em] text-slate-600 uppercase">
|
||||
{[
|
||||
"On-device AI",
|
||||
"Facts-first",
|
||||
"Patient-owned",
|
||||
"iOS and Android beta",
|
||||
].map((badge) => (
|
||||
<span key={badge} className="flex items-center gap-2">
|
||||
<span className="size-1 rounded-full bg-teal-600" />
|
||||
{badge}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto w-[186px] rounded-[28px] border-[6px] border-slate-950 bg-slate-950 p-1 shadow-[0_18px_45px_rgba(15,23,42,0.18)]">
|
||||
<div className="overflow-hidden rounded-[19px] bg-white">
|
||||
<Image
|
||||
src="/screenshots/medications.png"
|
||||
alt="Medscribe medications screen"
|
||||
width={1206}
|
||||
height={2622}
|
||||
priority
|
||||
unoptimized
|
||||
className="block h-auto w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 border-t border-teal-700/25 pt-5">
|
||||
<p className="northwell-print-label">What it can do</p>
|
||||
<h2 className="mt-2 font-serif text-[25px] leading-[1.12] font-semibold text-slate-950">
|
||||
Real medical AI, working entirely on the phone
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 grid grid-cols-3 gap-x-7 gap-y-7">
|
||||
{capabilities.map(({ icon: Icon, title, body }) => (
|
||||
<div key={title}>
|
||||
<Icon className="size-5 text-teal-700" strokeWidth={1.75} />
|
||||
<h3 className="mt-2.5 font-serif text-[16px] leading-[1.15] font-semibold text-slate-950">
|
||||
{title}
|
||||
</h3>
|
||||
<p className="mt-1.5 text-[10px] leading-[1.5] text-slate-600">
|
||||
{body}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 grid grid-cols-3 gap-7 border-t border-teal-700/25 pt-5">
|
||||
{[
|
||||
[
|
||||
"On the device, not the cloud",
|
||||
"Summarization and medication Q&A run on the phone. No health data goes to a server for AI, so there is no new breach surface and no BAA.",
|
||||
],
|
||||
[
|
||||
"Facts first, not guesswork",
|
||||
"Drug facts, classes, and interaction pairs come from bundled reference data. The model helps phrase the answer; it does not invent it.",
|
||||
],
|
||||
[
|
||||
"Owned by the patient",
|
||||
"One record that spans every provider, that the patient controls, carries, and decides how to share.",
|
||||
],
|
||||
].map(([title, body]) => (
|
||||
<div key={title}>
|
||||
<p className="font-serif text-[14px] font-semibold text-slate-950">
|
||||
{title}
|
||||
</p>
|
||||
<p className="mt-1.5 text-[9.5px] leading-[1.5] text-slate-600">
|
||||
{body}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</PrintPage>
|
||||
|
||||
<PrintPage page={2}>
|
||||
<section className="pt-3">
|
||||
<p className="northwell-print-label">Who it helps</p>
|
||||
<h2 className="northwell-print-title">Every capability pays off twice</h2>
|
||||
<p className="northwell-print-lede max-w-[660px]">
|
||||
The features that make Medscribe genuinely useful to the patient are
|
||||
the same ones that move the numbers CVS is measured on. Cross-provider
|
||||
by design; one central pharmacy in practice.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div className="mt-6 grid grid-cols-[0.92fr_1.08fr] gap-8">
|
||||
<p className="text-[9px] font-bold tracking-[0.18em] text-slate-500 uppercase">
|
||||
For the patient
|
||||
</p>
|
||||
<p className="text-[9px] font-bold tracking-[0.18em] text-teal-700 uppercase">
|
||||
For CVS
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-1">
|
||||
{payoffs.map((row) => (
|
||||
<div
|
||||
key={row.cvsTitle}
|
||||
className="grid grid-cols-[0.92fr_1.08fr] gap-8 border-t border-slate-200 py-3"
|
||||
>
|
||||
<div>
|
||||
<h3 className="font-serif text-[14px] leading-[1.2] font-semibold text-slate-950">
|
||||
{row.patientTitle}
|
||||
</h3>
|
||||
<p className="mt-1 text-[9.5px] leading-[1.45] text-slate-600">
|
||||
{row.patient}
|
||||
</p>
|
||||
</div>
|
||||
<div className="border-l border-teal-600/30 pl-8">
|
||||
<h3 className="font-serif text-[14px] leading-[1.2] font-semibold text-teal-900">
|
||||
{row.cvsTitle}
|
||||
</h3>
|
||||
<p className="mt-1 text-[9.5px] leading-[1.45] text-slate-600">
|
||||
{row.cvs}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p className="mt-5 bg-teal-50/70 px-5 py-3 text-[10px] leading-[1.5] text-slate-700">
|
||||
<span className="font-semibold text-slate-900">
|
||||
It sits beside the CVS stack, not on top of it.{" "}
|
||||
</span>
|
||||
Dispensing, Caremark benefits, and the CVS app keep doing their jobs.
|
||||
Medscribe is the patient-owned layer that feeds value back in with
|
||||
consent. A partnership unlocks co-branding, refill routing, and
|
||||
adherence reporting against Star and EQuIPP measures.
|
||||
</p>
|
||||
|
||||
<section className="mt-6 grid grid-cols-[1.12fr_0.88fr] items-start gap-9 border-t border-slate-300 pt-5">
|
||||
<div>
|
||||
<p className="northwell-print-label">The ask</p>
|
||||
<h3 className="mt-3 font-serif text-[20px] leading-[1.22] font-semibold text-slate-950">
|
||||
A short conversation about a record that moves adherence and keeps
|
||||
refills at CVS.
|
||||
</h3>
|
||||
<p className="mt-2 text-[10.5px] leading-[1.5] text-slate-600">
|
||||
Live in beta on iOS and Android today. The core patient experience
|
||||
ships now; the business layer is what a partnership builds together.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="northwell-print-label">Who is behind it</p>
|
||||
<h3 className="mt-3 font-serif text-[19px] font-semibold text-slate-950">
|
||||
Sean O'Connor
|
||||
</h3>
|
||||
<p className="mt-3 text-[10.5px] leading-[1.5] text-slate-600">
|
||||
A Bucknell computer science and engineering graduate who built
|
||||
Medscribe after watching his family lose the full picture of his
|
||||
grandfather's care.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</PrintPage>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,120 +1,122 @@
|
||||
"use client";
|
||||
|
||||
import type { KeyboardEvent } from "react";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
CalendarDays,
|
||||
Camera,
|
||||
Check,
|
||||
ContactRound,
|
||||
Home,
|
||||
MessageCircle,
|
||||
Shield,
|
||||
Pill,
|
||||
} from "lucide-react";
|
||||
|
||||
import { PhoneMockup } from "~/components/phone-mockup";
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
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",
|
||||
label: "Visits",
|
||||
icon: CalendarDays,
|
||||
title: "Visit recordings that refresh the record",
|
||||
title: "Turn a visit into context you can review",
|
||||
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: [
|
||||
"Consent-gated, on-device recording",
|
||||
"Searchable transcript and summary",
|
||||
"Follow-ups captured, not forgotten",
|
||||
"Supported transcription and summarization run on device",
|
||||
"The person reviews generated information",
|
||||
"Structured visit-to-record change review is still roadmap",
|
||||
],
|
||||
image: "/screenshots/visit-timeline.png",
|
||||
alt: "Medscribe visit timeline with appointment cards",
|
||||
image: "/screenshots/visit-detail.png",
|
||||
alt: "Synthetic Medscribe beta visit detail showing a visit summary and follow-up information",
|
||||
},
|
||||
{
|
||||
id: "meds",
|
||||
label: "Meds",
|
||||
icon: Camera,
|
||||
title: "Medication OCR, reminders, and history",
|
||||
id: "medications",
|
||||
label: "Medications",
|
||||
icon: Pill,
|
||||
title: "Keep the daily medication picture close",
|
||||
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: [
|
||||
"Scan labels with the camera",
|
||||
"Reminders and refill alerts",
|
||||
"Dose history at a glance",
|
||||
"Label text remains editable before it becomes the record",
|
||||
"Bundled facts and known pairs use named reference datasets",
|
||||
"Coverage is intentionally bounded, not a complete interaction check",
|
||||
],
|
||||
image: "/screenshots/medications.png",
|
||||
alt: "Medscribe medications screen with active medications",
|
||||
image: "/screenshots/medication-detail.png",
|
||||
alt: "Synthetic Medscribe beta medication detail with schedule and medication information",
|
||||
},
|
||||
{
|
||||
id: "ask",
|
||||
label: "Ask",
|
||||
id: "questions",
|
||||
label: "Questions",
|
||||
icon: MessageCircle,
|
||||
title: "Medication questions grounded in the record",
|
||||
title: "Facts first, fluent answers second",
|
||||
description:
|
||||
"Plain-language questions can draw from the person's record and bundled FDA facts, while keeping diagnosis and dosing with clinicians.",
|
||||
"Medication questions can use the person’s local record and bundled fields. Dosing changes, diagnosis, and treatment decisions stay with a clinician or pharmacist.",
|
||||
points: [
|
||||
"Plain-language answers",
|
||||
"Grounded in the record and FDA facts",
|
||||
"Defers dosing and diagnosis to clinicians",
|
||||
"Known facts render from bundled data",
|
||||
"The local model helps with conversational phrasing",
|
||||
"High-risk or unsupported questions defer to a professional",
|
||||
],
|
||||
image: "/screenshots/chat.png",
|
||||
alt: "Medscribe Ask chat answering a question about blood pressure medications",
|
||||
image: "/screenshots/chat-response.png",
|
||||
alt: "Synthetic Medscribe beta answer with medication context and a safety boundary",
|
||||
},
|
||||
{
|
||||
id: "privacy",
|
||||
label: "Privacy",
|
||||
icon: Shield,
|
||||
title: "Private by architecture, not policy",
|
||||
id: "handoff",
|
||||
label: "Know Me",
|
||||
icon: ContactRound,
|
||||
title: "Prepare one readable handoff",
|
||||
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: [
|
||||
"On-device AI, no cloud processing",
|
||||
"No inbox to breach or mine",
|
||||
"You control what is ever shared",
|
||||
"Medication schedules can include source and confirmation",
|
||||
"The person can show or share the snapshot they reviewed",
|
||||
"It complements—not replaces—clinical verification",
|
||||
],
|
||||
image: "/screenshots/settings.png",
|
||||
alt: "Medscribe settings showing on-device AI models and privacy notice",
|
||||
image: "/screenshots/emergency-record.png",
|
||||
alt: "Synthetic Medscribe beta Know Me handoff with active medications and review state",
|
||||
},
|
||||
] as const;
|
||||
|
||||
type FeatureId = (typeof features)[number]["id"];
|
||||
|
||||
export function FeatureShowcase() {
|
||||
const [activeId, setActiveId] = useState<(typeof features)[number]["id"]>("home");
|
||||
const active = features.find((f) => f.id === activeId) ?? features[0];
|
||||
const [activeId, setActiveId] = useState<FeatureId>("visits");
|
||||
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 (
|
||||
<div className="grid items-center gap-8 lg:grid-cols-[minmax(0,1fr)_300px] lg:gap-12">
|
||||
<div className="flex flex-col gap-5">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<div className="grid items-center gap-10 lg:grid-cols-[minmax(0,1fr)_340px] lg:gap-16">
|
||||
<div className="flex min-w-0 flex-col gap-6">
|
||||
<div
|
||||
role="tablist"
|
||||
aria-label="Explore the Medscribe beta"
|
||||
className="flex max-w-full gap-2 overflow-x-auto pb-1"
|
||||
>
|
||||
{features.map((feature) => {
|
||||
const Icon = feature.icon;
|
||||
const isActive = feature.id === activeId;
|
||||
@@ -122,49 +124,67 @@ export function FeatureShowcase() {
|
||||
return (
|
||||
<button
|
||||
key={feature.id}
|
||||
id={`feature-tab-${feature.id}`}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={isActive}
|
||||
aria-controls={`feature-panel-${feature.id}`}
|
||||
tabIndex={isActive ? 0 : -1}
|
||||
onClick={() => setActiveId(feature.id)}
|
||||
onKeyDown={onTabKeyDown}
|
||||
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
|
||||
? "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}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-serif text-2xl font-semibold tracking-tight text-foreground sm:text-3xl">
|
||||
<div
|
||||
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}
|
||||
</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}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ul className="grid gap-x-5 gap-y-2.5 sm:grid-cols-2">
|
||||
{active.points.map((point) => (
|
||||
<li
|
||||
key={point}
|
||||
className="flex items-start gap-2 text-sm leading-snug text-foreground"
|
||||
>
|
||||
<span className="mt-0.5 flex size-4 shrink-0 items-center justify-center rounded-full bg-primary-light">
|
||||
<Check className="size-3 text-primary" />
|
||||
</span>
|
||||
<span>{point}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<ul className="mt-6 grid gap-3 sm:grid-cols-2">
|
||||
{active.points.map((point) => (
|
||||
<li
|
||||
key={point}
|
||||
className="text-foreground flex items-start gap-2.5 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>
|
||||
<span>{point}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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)]">
|
||||
<PhoneMockup src={active.image} alt={active.alt} width={188} />
|
||||
<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={220} />
|
||||
<p className="text-muted-foreground mt-4 text-center text-xs">
|
||||
Synthetic demo data · beta interface
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,381 @@
|
||||
import Image from "next/image";
|
||||
|
||||
function PrintPage({
|
||||
page,
|
||||
children,
|
||||
}: Readonly<{ page: number; children: React.ReactNode }>) {
|
||||
return (
|
||||
<article
|
||||
className={
|
||||
page === 1
|
||||
? "northwell-print-page"
|
||||
: "northwell-print-page northwell-print-page--continuation"
|
||||
}
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img className="northwell-print-wash" src="/print-wash.svg" alt="" />
|
||||
{page === 1 ? (
|
||||
<header className="northwell-print-header">
|
||||
<div className="flex items-center gap-2.5">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img className="h-6 w-auto" src="/medscribe-ms-logo.svg" alt="" />
|
||||
<span className="font-serif text-[17px] font-semibold text-slate-950">
|
||||
Medscribe
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-[9px] font-semibold tracking-[0.18em] text-slate-500 uppercase">
|
||||
Executive summary · Northwell Health
|
||||
</span>
|
||||
</header>
|
||||
) : null}
|
||||
|
||||
<div className="northwell-print-content">{children}</div>
|
||||
|
||||
<footer className="northwell-print-footer">
|
||||
<span>Page {page} of 3</span>
|
||||
</footer>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
const people = [
|
||||
[
|
||||
"The student on their own",
|
||||
"Managing prescriptions and appointments for the first time, away from the parent who used to track it all.",
|
||||
],
|
||||
[
|
||||
"The newly diagnosed",
|
||||
"A new routine, new instructions, and follow-ups that are a blur by the next visit.",
|
||||
],
|
||||
[
|
||||
"The long-distance caregiver",
|
||||
"An adult child coordinating a parent's medications with siblings who cover different days.",
|
||||
],
|
||||
[
|
||||
"The emergency",
|
||||
"An older patient at an out-of-network ER, where no one has met them or knows which medications cannot be delayed.",
|
||||
],
|
||||
] as const;
|
||||
|
||||
const productPillars = [
|
||||
[
|
||||
"Capture visits",
|
||||
"Type or paste notes, import a portal screenshot, attach a document, or use consent-gated recording. Summarization stays on device.",
|
||||
],
|
||||
[
|
||||
"Keep medications current",
|
||||
"Pill-bottle label recognition, reminders, refill tracking, dose history, and questions grounded in bundled FDA facts.",
|
||||
],
|
||||
[
|
||||
"Be ready to hand off",
|
||||
"A patient-controlled Know Me record and encrypted caregiver sharing keep critical context ready when the chart is incomplete.",
|
||||
],
|
||||
] as const;
|
||||
|
||||
const northwellFit = [
|
||||
[
|
||||
"AI patients can trust",
|
||||
"Visit summarization and medication Q&A run on the phone, while medication facts come from bundled reference data rather than model recall. It is a clear AI posture for patients, privacy teams, and clinicians.",
|
||||
],
|
||||
[
|
||||
"A Northwell story worth telling",
|
||||
"Northwell can lead with a concrete patient benefit: one private, emergency-ready picture that travels across systems. The experience could carry Northwell's brand without pretending to replace MyChart.",
|
||||
],
|
||||
[
|
||||
"An everyday touchstone",
|
||||
"Reminders, refills, dose tracking, questions, and record updates create useful reasons to return between appointments. Each moment can point the patient back to the Northwell care team.",
|
||||
],
|
||||
] as const;
|
||||
|
||||
const recordContents = [
|
||||
"Active medications, doses, schedules, and refill status",
|
||||
"Allergies, conditions, baseline, and the context that changes a handoff",
|
||||
"Visit summaries, follow-up instructions, and questions for the next visit",
|
||||
"Emergency contacts and a concise, patient-controlled Know Me record",
|
||||
] as const;
|
||||
|
||||
export function NorthwellPrintSummary() {
|
||||
return (
|
||||
<div className="northwell-print-only" aria-hidden="true">
|
||||
<PrintPage page={1}>
|
||||
<div className="grid grid-cols-[1.16fr_0.84fr] items-start gap-9 pt-5">
|
||||
<div>
|
||||
<p className="northwell-print-label">The patient-owned record</p>
|
||||
<h1 className="mt-4 font-serif text-[44px] leading-[0.98] font-semibold tracking-[-0.035em] text-slate-950">
|
||||
Be known when care gets complicated
|
||||
</h1>
|
||||
<p className="mt-5 text-[16px] leading-[1.55] text-slate-600">
|
||||
Medscribe keeps a patient's medications, visits, baseline,
|
||||
and critical care context current between appointments, then makes
|
||||
that picture ready for a caregiver, a new doctor, or an ER.
|
||||
</p>
|
||||
<div className="mt-6 grid grid-cols-2 gap-x-5 gap-y-2 text-[8.5px] font-bold tracking-[0.08em] text-slate-600 uppercase">
|
||||
{[
|
||||
"On-device AI",
|
||||
"Emergency-ready",
|
||||
"Patient-owned",
|
||||
"iOS and Android beta",
|
||||
].map((badge) => (
|
||||
<span key={badge} className="flex items-center gap-2">
|
||||
<span className="size-1 rounded-full bg-teal-600" />
|
||||
{badge}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto w-[194px] rounded-[28px] border-[6px] border-slate-950 bg-slate-950 p-1 shadow-[0_18px_45px_rgba(15,23,42,0.18)]">
|
||||
<div className="overflow-hidden rounded-[19px] bg-white">
|
||||
<Image
|
||||
src="/screenshots/home.png"
|
||||
alt="Medscribe home screen"
|
||||
width={1206}
|
||||
height={2622}
|
||||
priority
|
||||
unoptimized
|
||||
className="block h-auto w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-7 border-t border-teal-700/25 pt-5">
|
||||
<p className="northwell-print-label">The job</p>
|
||||
<p className="mt-3 font-serif text-[25px] leading-[1.2] font-semibold text-slate-950">
|
||||
One trustworthy picture that survives fragmented care, missed
|
||||
appointments, secondhand updates, and emergency handoffs.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 grid grid-cols-3 gap-5">
|
||||
{[
|
||||
[
|
||||
"Keep it current",
|
||||
"Everyday visit and medication workflows maintain the record.",
|
||||
],
|
||||
[
|
||||
"Make it obvious",
|
||||
"Time-critical medications and baseline context rise to the top.",
|
||||
],
|
||||
[
|
||||
"Keep it controlled",
|
||||
"The patient decides what is shared, when, and with whom.",
|
||||
],
|
||||
].map(([title, body]) => (
|
||||
<div key={title} className="pr-3">
|
||||
<p className="font-serif text-[16px] font-semibold text-slate-950">
|
||||
{title}
|
||||
</p>
|
||||
<p className="mt-2 text-[10.5px] leading-[1.5] text-slate-600">
|
||||
{body}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-6 grid grid-cols-[0.72fr_1.28fr] gap-5">
|
||||
<div>
|
||||
<p className="northwell-print-label">One living picture</p>
|
||||
<p className="mt-2 text-[10.5px] leading-[1.5] text-slate-600">
|
||||
The record is useful in an emergency because ordinary use keeps it
|
||||
from becoming a stale export.
|
||||
</p>
|
||||
</div>
|
||||
<ul className="grid grid-cols-2 gap-x-5 gap-y-2 text-[9.5px] leading-[1.45] text-slate-600">
|
||||
{recordContents.map((item) => (
|
||||
<li
|
||||
key={item}
|
||||
className="relative pl-3 before:absolute before:top-[0.42em] before:left-0 before:size-1 before:rounded-full before:bg-teal-500 before:content-['']"
|
||||
>
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</PrintPage>
|
||||
|
||||
<PrintPage page={2}>
|
||||
<section className="pt-3">
|
||||
<p className="northwell-print-label">What it is</p>
|
||||
<h2 className="northwell-print-title">
|
||||
A medical record the patient owns and carries
|
||||
</h2>
|
||||
<p className="northwell-print-lede">
|
||||
The daily tools are not separate features. They are the maintenance
|
||||
system for one emergency-ready record.
|
||||
</p>
|
||||
<div className="mt-5 grid grid-cols-3 gap-5">
|
||||
{productPillars.map(([title, body], index) => (
|
||||
<div key={title} className="min-h-[120px] pr-3">
|
||||
<span className="text-[10px] font-bold text-teal-700">
|
||||
0{index + 1}
|
||||
</span>
|
||||
<h3 className="mt-2 font-serif text-[17px] font-semibold text-slate-950">
|
||||
{title}
|
||||
</h3>
|
||||
<p className="mt-2 text-[10.5px] leading-[1.5] text-slate-600">
|
||||
{body}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-7">
|
||||
<p className="northwell-print-label">Who it is for</p>
|
||||
<h2 className="northwell-print-title">
|
||||
Anyone holding care together
|
||||
</h2>
|
||||
<div className="mt-4 grid grid-cols-2 gap-x-7 gap-y-2">
|
||||
{people.map(([title, body]) => (
|
||||
<div key={title} className="min-h-[70px] pr-4">
|
||||
<h3 className="font-serif text-[15px] font-semibold text-slate-950">
|
||||
{title}
|
||||
</h3>
|
||||
<p className="mt-1.5 text-[10.5px] leading-[1.45] text-slate-600">
|
||||
{body}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-7 border-l-4 border-teal-600 bg-teal-50/50 py-5 pr-5 pl-6">
|
||||
<p className="northwell-print-label">What makes it different</p>
|
||||
<h2 className="mt-3 font-serif text-[24px] font-semibold text-slate-950">
|
||||
Every alternative owns a slice. Medscribe owns the whole job.
|
||||
</h2>
|
||||
<div className="mt-3 grid grid-cols-2 gap-6 text-[10.5px] leading-[1.55] text-slate-600">
|
||||
<p>
|
||||
Every institutional record stops at its own walls. Epic aggregates
|
||||
Epic; urgent care, community specialists, and facilities on other
|
||||
systems can still sit outside the picture in front of a clinician.
|
||||
</p>
|
||||
<p>
|
||||
Visit notetakers, medication trackers, record vaults, and
|
||||
emergency IDs each solve one slice. Medscribe connects daily
|
||||
maintenance to the handoff across Epic and non-Epic care, on the
|
||||
patient's device and under the patient's control.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-5 grid grid-cols-2 gap-7 border-t border-slate-300 pt-5">
|
||||
<div>
|
||||
<p className="northwell-print-label">Facts first</p>
|
||||
<p className="mt-2 text-[10px] leading-[1.5] text-slate-600">
|
||||
Medication facts, drug classes, and known interaction pairs come
|
||||
from bundled reference data. The language model helps with
|
||||
phrasing; it is not treated as the source of medical facts.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="northwell-print-label">Local by default</p>
|
||||
<p className="mt-2 text-[10px] leading-[1.5] text-slate-600">
|
||||
Visit summarization and medication Q&A run on the phone.
|
||||
Caregiver sharing is end-to-end encrypted, so a server is not
|
||||
asked to read the patient's record in order to coordinate
|
||||
care.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</PrintPage>
|
||||
|
||||
<PrintPage page={3}>
|
||||
<section className="pt-3">
|
||||
<p className="northwell-print-label">The Northwell opportunity</p>
|
||||
<h2 className="northwell-print-title">
|
||||
A trusted AI touchstone between visits
|
||||
</h2>
|
||||
<p className="northwell-print-lede max-w-[650px]">
|
||||
Medscribe gives Northwell a useful presence in the part of care no
|
||||
portal owns: the ordinary days when patients remember, refill,
|
||||
question, coordinate, and try to follow the plan.
|
||||
</p>
|
||||
<div className="mt-6 grid grid-cols-3 gap-7">
|
||||
{northwellFit.map(([title, body], index) => (
|
||||
<div key={title}>
|
||||
<span className="text-[10px] font-bold text-teal-700">
|
||||
0{index + 1}
|
||||
</span>
|
||||
<h3 className="font-serif text-[16px] font-semibold text-slate-950">
|
||||
{title}
|
||||
</h3>
|
||||
<p className="mt-2 text-[10px] leading-[1.55] text-slate-600">
|
||||
{body}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-7 bg-teal-50/60 px-6 py-5">
|
||||
<p className="northwell-print-label">The touchstone</p>
|
||||
<h2 className="mt-2 font-serif text-[24px] leading-[1.18] font-semibold text-slate-950">
|
||||
MyChart organizes the appointment. Medscribe helps the patient live
|
||||
the plan.
|
||||
</h2>
|
||||
<p className="mt-3 text-[11px] leading-[1.55] text-slate-600">
|
||||
A reminder, a refill, a quick question, or an update to the record
|
||||
becomes a small moment of continuity. Presented as part of the
|
||||
Northwell experience, those moments can reinforce the care plan and
|
||||
point the patient back to the team that knows them.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mt-6 border-t border-slate-300 pt-5">
|
||||
<p className="northwell-print-label">How it fits the stack</p>
|
||||
<div className="mt-4 grid grid-cols-2 gap-9">
|
||||
<div>
|
||||
<h3 className="font-serif text-[16px] font-semibold text-slate-950">
|
||||
Epic remains the record of care
|
||||
</h3>
|
||||
<p className="mt-2 text-[10.5px] leading-[1.55] text-slate-600">
|
||||
Northwell's clinical workflows, orders, results, messages,
|
||||
and institutional history stay where they belong. Medscribe is
|
||||
not another EHR and does not need to displace MyChart.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-serif text-[16px] font-semibold text-slate-950">
|
||||
The patient carries the missing context
|
||||
</h3>
|
||||
<p className="mt-2 text-[10.5px] leading-[1.55] text-slate-600">
|
||||
Non-Epic visits, medication changes, caregiver knowledge, and
|
||||
emergency context stay with the patient. Consent governs when
|
||||
that picture is shared back into a Northwell interaction.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-6 grid grid-cols-[1.12fr_0.88fr] gap-9 border-t border-slate-300 pt-5">
|
||||
<div>
|
||||
<p className="northwell-print-label">The marketing value</p>
|
||||
<h3 className="mt-3 font-serif text-[19px] leading-[1.25] font-semibold text-slate-950">
|
||||
A patient promise people can understand
|
||||
</h3>
|
||||
<p className="mt-2 text-[10.5px] leading-[1.5] text-slate-600">
|
||||
Private AI. One current picture. Ready for the caregiver, the new
|
||||
doctor, or the ER. That is a more human story than “AI in
|
||||
healthcare,” and it gives Northwell a visible way to stand
|
||||
for continuity beyond its own walls.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="northwell-print-label">Built and live</p>
|
||||
<h3 className="mt-3 font-serif text-[19px] font-semibold text-slate-950">
|
||||
Sean O'Connor
|
||||
</h3>
|
||||
<p className="mt-3 text-[10.5px] leading-[1.5] text-slate-600">
|
||||
A Bucknell computer science and engineering graduate who built
|
||||
Medscribe after watching his family lose the full picture of his
|
||||
grandfather's care. The product is live in beta on iOS and
|
||||
Android today.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</PrintPage>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { IPhoneMockup } from "react-device-mockup";
|
||||
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
@@ -27,33 +24,26 @@ export function PhoneMockup({
|
||||
const isSimulatorCapture = captureMode === "simulator";
|
||||
|
||||
return (
|
||||
<div className={cn("mx-auto w-fit drop-shadow-[0_18px_45px_rgba(15,23,42,0.14)]", className)}>
|
||||
<IPhoneMockup
|
||||
screenWidth={width}
|
||||
screenType="island"
|
||||
frameColor="#111827"
|
||||
statusbarColor="#f8fafc"
|
||||
hideStatusBar
|
||||
hideNavBar
|
||||
transparentNavBar
|
||||
>
|
||||
<div className="relative h-full w-full overflow-hidden bg-surface">
|
||||
<Image
|
||||
src={src}
|
||||
alt={alt}
|
||||
width={1206}
|
||||
height={2622}
|
||||
priority={priority}
|
||||
unoptimized
|
||||
className={cn(
|
||||
"h-full w-full object-cover",
|
||||
isSimulatorCapture
|
||||
? "scale-[1.22] object-[50%_58%]"
|
||||
: "object-top",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</IPhoneMockup>
|
||||
<div
|
||||
className={cn(
|
||||
"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)]",
|
||||
className,
|
||||
)}
|
||||
style={{ width }}
|
||||
>
|
||||
<div className="bg-surface relative aspect-[1206/2622] overflow-hidden rounded-[1.65rem]">
|
||||
<Image
|
||||
src={src}
|
||||
alt={alt}
|
||||
fill
|
||||
priority={priority}
|
||||
sizes={`${width}px`}
|
||||
className={cn(
|
||||
"object-cover",
|
||||
isSimulatorCapture ? "scale-[1.22] object-[50%_58%]" : "object-top",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import Image from "next/image";
|
||||
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
type ProductDeviceProps = {
|
||||
src: string;
|
||||
alt: string;
|
||||
className?: string;
|
||||
sizes?: string;
|
||||
priority?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* A precomposited product capture using the licensed device bezels in
|
||||
* `public/screenshots/pitch`. Keeping the frame in the source image preserves
|
||||
* the bezel details without adding layout or client-side work.
|
||||
*/
|
||||
export function ProductDevice({
|
||||
src,
|
||||
alt,
|
||||
className,
|
||||
sizes = "(min-width: 1024px) 360px, (min-width: 640px) 320px, 72vw",
|
||||
priority = false,
|
||||
}: ProductDeviceProps) {
|
||||
return (
|
||||
<Image
|
||||
src={src}
|
||||
alt={alt}
|
||||
width={810}
|
||||
height={1656}
|
||||
sizes={sizes}
|
||||
priority={priority}
|
||||
className={cn(
|
||||
"h-auto w-full drop-shadow-[0_35px_35px_rgba(15,23,42,0.22)] dark:drop-shadow-[0_38px_42px_rgba(0,0,0,0.5)]",
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,19 @@
|
||||
export function SectionLabel({ children }: { children: React.ReactNode }) {
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
export function SectionLabel({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<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}
|
||||
</p>
|
||||
);
|
||||
|
||||
@@ -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() {
|
||||
return (
|
||||
<footer className="print-hidden border-t border-border-soft bg-surface/70">
|
||||
<div className="mx-auto flex max-w-6xl flex-col gap-4 px-4 py-10 text-sm text-muted-foreground sm:px-6 sm:flex-row sm:items-center sm:justify-between">
|
||||
<footer className="print-hidden border-border-soft bg-surface/80 border-t">
|
||||
<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>
|
||||
<p className="font-serif text-base font-semibold text-foreground">Medscribe</p>
|
||||
<p className="mt-1 max-w-md leading-relaxed">
|
||||
Be known when care gets complicated.
|
||||
<Link
|
||||
href="/"
|
||||
className="text-foreground focus-visible:ring-primary/40 inline-flex items-center gap-2.5 rounded-md outline-none focus-visible:ring-2"
|
||||
>
|
||||
<MedscribeLogo className="h-7 w-auto" />
|
||||
<span className="font-serif text-lg font-semibold">Medscribe</span>
|
||||
</Link>
|
||||
<p className="text-muted-foreground mt-4 max-w-md leading-relaxed">
|
||||
A private, patient-controlled beta for keeping visits, medications,
|
||||
and critical context together on the device you carry.
|
||||
</p>
|
||||
<p className="text-muted-foreground mt-3 text-xs leading-relaxed">
|
||||
Medscribe does not diagnose, prescribe, or replace emergency
|
||||
services or a clinician's verification.
|
||||
</p>
|
||||
</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>
|
||||
<p>
|
||||
<p className="text-foreground font-semibold">Contact</p>
|
||||
<p className="text-muted-foreground mt-3 leading-relaxed">
|
||||
Built by{" "}
|
||||
<a
|
||||
href="https://soconnor.dev"
|
||||
className="text-foreground underline-offset-4 hover:underline"
|
||||
className="text-foreground focus-visible:ring-primary/40 rounded-sm underline-offset-4 outline-none hover:underline focus-visible:ring-2"
|
||||
>
|
||||
Sean O'Connor
|
||||
</a>
|
||||
</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">
|
||||
© {new Date().getFullYear()} Hadlock Holdings LLC
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -1,133 +1,74 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { FileText, Home, Menu, X } from "lucide-react";
|
||||
import { Menu } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
import { MedscribeLogo } from "~/components/medscribe-logo";
|
||||
import { cn } from "~/lib/utils";
|
||||
import { Button } from "~/components/ui/button";
|
||||
|
||||
const navLinks = [
|
||||
{ href: "/", label: "About", icon: Home },
|
||||
{ href: "/executive-summary", label: "Summary", icon: FileText },
|
||||
];
|
||||
const navigation = [
|
||||
{ href: "/how-it-works", label: "How it works" },
|
||||
{ href: "/privacy", label: "Privacy & evidence" },
|
||||
] as const;
|
||||
|
||||
export function SiteHeader() {
|
||||
const pathname = usePathname();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<header className="print-hidden sticky top-3 z-50 px-3 sm:px-6">
|
||||
<div className="relative mx-auto max-w-5xl">
|
||||
<div className="flex h-14 items-center justify-between gap-4 rounded-full border border-border-soft/80 bg-canvas/82 px-4 shadow-[0_10px_30px_rgba(15,23,42,0.08)] backdrop-blur-md sm:px-5">
|
||||
<Link
|
||||
href="/"
|
||||
className="flex items-center text-foreground transition-opacity hover:opacity-80"
|
||||
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",
|
||||
)}
|
||||
<header className="print-hidden border-border-soft/70 bg-canvas/88 sticky top-0 z-50 border-b backdrop-blur-lg">
|
||||
<div className="mx-auto flex h-16 max-w-6xl items-center gap-5 px-4 sm:px-6">
|
||||
<Link
|
||||
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"
|
||||
aria-label="Medscribe home"
|
||||
>
|
||||
<nav className="flex flex-col p-2">
|
||||
{navLinks.map((link) => {
|
||||
const isActive = link.href === pathname;
|
||||
const Icon = link.icon;
|
||||
<MedscribeLogo className="h-7 w-auto" />
|
||||
<span className="font-serif text-lg font-semibold tracking-tight">
|
||||
Medscribe
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
aria-current={isActive ? "page" : undefined}
|
||||
onClick={() => setIsOpen(false)}
|
||||
className={cn(
|
||||
"flex items-center gap-3 rounded-xl px-3 py-2.5 text-sm font-medium transition-colors",
|
||||
isActive
|
||||
? "bg-primary-light/60 text-foreground"
|
||||
: "text-muted-foreground hover:bg-surface-soft hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
<Icon className="size-4" />
|
||||
{link.label}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
<nav
|
||||
aria-label="Primary navigation"
|
||||
className="ml-auto hidden items-center gap-1 md:flex"
|
||||
>
|
||||
{navigation.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className="text-muted-foreground hover:bg-surface-soft hover:text-foreground focus-visible:ring-primary/40 rounded-lg px-3 py-2 text-sm font-medium transition-colors outline-none focus-visible:ring-2"
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
<Button asChild size="sm" className="ml-2">
|
||||
<Link href="/beta">Request beta access</Link>
|
||||
</Button>
|
||||
</nav>
|
||||
|
||||
<details className="group relative ml-auto md:hidden">
|
||||
<summary className="border-border-soft bg-surface text-foreground focus-visible:ring-primary/40 flex size-11 cursor-pointer list-none items-center justify-center rounded-xl border outline-none marker:content-none focus-visible:ring-2 [&::-webkit-details-marker]:hidden">
|
||||
<Menu className="size-5" aria-hidden="true" />
|
||||
<span className="sr-only">Open navigation</span>
|
||||
</summary>
|
||||
<nav
|
||||
aria-label="Mobile navigation"
|
||||
className="border-border-soft bg-surface absolute top-13 right-0 flex w-64 flex-col rounded-2xl border p-2 shadow-[0_18px_50px_rgba(15,23,42,0.16)]"
|
||||
>
|
||||
{navigation.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className="text-foreground hover:bg-surface-soft focus-visible:ring-primary/40 rounded-xl px-4 py-3 text-sm font-medium outline-none focus-visible:ring-2"
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
<Link
|
||||
href="/beta"
|
||||
className="bg-primary-button hover:bg-primary-button/90 focus-visible:ring-primary/40 mt-1 rounded-xl px-4 py-3 text-center text-sm font-semibold text-white outline-none focus-visible:ring-2"
|
||||
>
|
||||
Request beta access
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
</details>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
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: {
|
||||
variant: {
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
import Image from "next/image";
|
||||
import {
|
||||
HeartPulse,
|
||||
Layers,
|
||||
Mic,
|
||||
MessageSquare,
|
||||
ScanLine,
|
||||
ShieldCheck,
|
||||
} from "lucide-react";
|
||||
|
||||
/**
|
||||
* Pharmacy-specific print summary (Walgreens variant). Page 1 (the product) is
|
||||
* identical to the CVS piece; page 2's "For Walgreens" column reflects that
|
||||
* Walgreens does not own a PBM or insurer the way CVS owns Caremark and Aetna,
|
||||
* so adherence and cost value flow to pharmacy performance reimbursement and to
|
||||
* Walgreens' plan and value-based-care partners, not an owned plan.
|
||||
* Reuses the `northwell-print-*` sheet/label classes from globals.css as the
|
||||
* page frame only.
|
||||
*/
|
||||
|
||||
function PrintPage({
|
||||
page,
|
||||
children,
|
||||
}: Readonly<{ page: number; children: React.ReactNode }>) {
|
||||
return (
|
||||
<article
|
||||
className={
|
||||
page === 1
|
||||
? "northwell-print-page"
|
||||
: "northwell-print-page northwell-print-page--continuation"
|
||||
}
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img className="northwell-print-wash" src="/print-wash.svg" alt="" />
|
||||
{page === 1 ? (
|
||||
<header className="northwell-print-header">
|
||||
<div className="flex items-center gap-2.5">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img className="h-6 w-auto" src="/medscribe-ms-logo.svg" alt="" />
|
||||
<span className="font-serif text-[17px] font-semibold text-slate-950">
|
||||
Medscribe
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-[9px] font-semibold tracking-[0.18em] text-slate-500 uppercase">
|
||||
Executive summary · Walgreens
|
||||
</span>
|
||||
</header>
|
||||
) : null}
|
||||
|
||||
<div className="northwell-print-content">{children}</div>
|
||||
|
||||
<footer className="northwell-print-footer">
|
||||
<span>Page {page} of 2</span>
|
||||
</footer>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
const capabilities = [
|
||||
{
|
||||
icon: Mic,
|
||||
title: "Understand a visit",
|
||||
body: "Type, paste, snap the after-visit summary, or record the conversation with consent. Medscribe transcribes and summarizes it on the phone.",
|
||||
},
|
||||
{
|
||||
icon: ScanLine,
|
||||
title: "Read a pill bottle",
|
||||
body: "Point the camera at a prescription label and the drug, dose, and schedule land in the record automatically.",
|
||||
},
|
||||
{
|
||||
icon: MessageSquare,
|
||||
title: "Answer the hard questions",
|
||||
body: "Ask whether two medications are safe together and get an answer grounded in bundled FDA drug facts and known interaction pairs, not a chatbot's guess.",
|
||||
},
|
||||
{
|
||||
icon: Layers,
|
||||
title: "Hold the whole picture",
|
||||
body: "Medications, allergies, conditions, baseline, and visit history across every provider and pharmacy, in one place the patient owns.",
|
||||
},
|
||||
{
|
||||
icon: HeartPulse,
|
||||
title: "Be ready for an emergency",
|
||||
body: "A patient-controlled Know Me card puts the time-critical facts one tap away for a caregiver or an ER.",
|
||||
},
|
||||
{
|
||||
icon: ShieldCheck,
|
||||
title: "Share without exposure",
|
||||
body: "End-to-end encrypted sharing lets a caregiver help, without a server ever reading the record.",
|
||||
},
|
||||
] as const;
|
||||
|
||||
const payoffs = [
|
||||
{
|
||||
patientTitle: "One picture, everywhere they are treated",
|
||||
patient:
|
||||
"Nothing falls through the cracks between a specialist, an urgent care, and the pharmacy.",
|
||||
bizTitle: "One central pharmacy",
|
||||
biz: "Refills route back to Walgreens and scattered scripts consolidate, instead of leaking to Amazon, CVS, Cost Plus, or mail order. Share of scripts, won.",
|
||||
},
|
||||
{
|
||||
patientTitle: "Medications that fit their day",
|
||||
patient:
|
||||
"Reminders and refill nudges that actually help, so they stay on their medications.",
|
||||
bizTitle: "Pharmacy quality and Star Ratings",
|
||||
biz: "The diabetes, hypertension, and statin adherence measures are triple-weighted in Part D Star Ratings, which drives performance reimbursement (EQuIPP, performance-based networks) and quality for the plans Walgreens serves.",
|
||||
},
|
||||
{
|
||||
patientTitle: "Fewer dangerous surprises",
|
||||
patient:
|
||||
"Interactions and duplicate prescriptions caught across every prescriber, not one pharmacy's slice.",
|
||||
bizTitle: "Lower cost of care",
|
||||
biz: "Fewer adverse events and ER visits: medical spend saved for the plan and value-based-care partners Walgreens shares members with.",
|
||||
},
|
||||
{
|
||||
patientTitle: "Ready at the counter",
|
||||
patient:
|
||||
"They arrive with a clean list and their routine questions already answered.",
|
||||
bizTitle: "Pharmacist leverage",
|
||||
biz: "Less low-value counter volume, more time for the billable MTM and CMR work pharmacists already do.",
|
||||
},
|
||||
{
|
||||
patientTitle: "A tool that is theirs",
|
||||
patient:
|
||||
"An advocate they control, on their own device. Not a sales pitch, and never on a server.",
|
||||
bizTitle: "Engagement without the liability",
|
||||
biz: "A Walgreens-branded habit patients actually open through myWalgreens, with no PHI on a server: no new breach surface and no BAA to negotiate.",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export function WalgreensPrintSummary() {
|
||||
return (
|
||||
<div className="northwell-print-only" aria-hidden="true">
|
||||
<PrintPage page={1}>
|
||||
<div className="grid grid-cols-[1.16fr_0.84fr] items-start gap-9 pt-5">
|
||||
<div>
|
||||
<p className="northwell-print-label">What Medscribe is</p>
|
||||
<h1 className="mt-4 font-serif text-[40px] leading-[1.0] font-semibold tracking-[-0.035em] text-slate-950">
|
||||
It captures, understands, and carries the patient's whole
|
||||
medical picture.
|
||||
</h1>
|
||||
<p className="mt-5 text-[15px] leading-[1.55] text-slate-600">
|
||||
Real medical AI on the patient's phone. Medscribe summarizes
|
||||
visits, reads pill bottles, answers medication questions from
|
||||
bundled drug facts, and keeps one emergency-ready record across
|
||||
every doctor and pharmacy. Nothing is sent to the cloud.
|
||||
</p>
|
||||
<div className="mt-6 grid grid-cols-2 gap-x-5 gap-y-2 text-[8.5px] font-bold tracking-[0.08em] text-slate-600 uppercase">
|
||||
{[
|
||||
"On-device AI",
|
||||
"Facts-first",
|
||||
"Patient-owned",
|
||||
"iOS and Android beta",
|
||||
].map((badge) => (
|
||||
<span key={badge} className="flex items-center gap-2">
|
||||
<span className="size-1 rounded-full bg-teal-600" />
|
||||
{badge}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto w-[186px] rounded-[28px] border-[6px] border-slate-950 bg-slate-950 p-1 shadow-[0_18px_45px_rgba(15,23,42,0.18)]">
|
||||
<div className="overflow-hidden rounded-[19px] bg-white">
|
||||
<Image
|
||||
src="/screenshots/medications.png"
|
||||
alt="Medscribe medications screen"
|
||||
width={1206}
|
||||
height={2622}
|
||||
priority
|
||||
unoptimized
|
||||
className="block h-auto w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 border-t border-teal-700/25 pt-5">
|
||||
<p className="northwell-print-label">What it can do</p>
|
||||
<h2 className="mt-2 font-serif text-[25px] leading-[1.12] font-semibold text-slate-950">
|
||||
Real medical AI, working entirely on the phone
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 grid grid-cols-3 gap-x-7 gap-y-7">
|
||||
{capabilities.map(({ icon: Icon, title, body }) => (
|
||||
<div key={title}>
|
||||
<Icon className="size-5 text-teal-700" strokeWidth={1.75} />
|
||||
<h3 className="mt-2.5 font-serif text-[16px] leading-[1.15] font-semibold text-slate-950">
|
||||
{title}
|
||||
</h3>
|
||||
<p className="mt-1.5 text-[10px] leading-[1.5] text-slate-600">
|
||||
{body}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 grid grid-cols-3 gap-7 border-t border-teal-700/25 pt-5">
|
||||
{[
|
||||
[
|
||||
"On the device, not the cloud",
|
||||
"Summarization and medication Q&A run on the phone. No health data goes to a server for AI, so there is no new breach surface and no BAA.",
|
||||
],
|
||||
[
|
||||
"Facts first, not guesswork",
|
||||
"Drug facts, classes, and interaction pairs come from bundled reference data. The model helps phrase the answer; it does not invent it.",
|
||||
],
|
||||
[
|
||||
"Owned by the patient",
|
||||
"One record that spans every provider and pharmacy, that the patient controls, carries, and decides how to share.",
|
||||
],
|
||||
].map(([title, body]) => (
|
||||
<div key={title}>
|
||||
<p className="font-serif text-[14px] font-semibold text-slate-950">
|
||||
{title}
|
||||
</p>
|
||||
<p className="mt-1.5 text-[9.5px] leading-[1.5] text-slate-600">
|
||||
{body}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</PrintPage>
|
||||
|
||||
<PrintPage page={2}>
|
||||
<section className="pt-3">
|
||||
<p className="northwell-print-label">Who it helps</p>
|
||||
<h2 className="northwell-print-title">Every capability pays off twice</h2>
|
||||
<p className="northwell-print-lede max-w-[660px]">
|
||||
The features that make Medscribe genuinely useful to the patient are
|
||||
the same ones that move the numbers Walgreens is measured on.
|
||||
Cross-provider by design; one central pharmacy in practice.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div className="mt-6 grid grid-cols-[0.92fr_1.08fr] gap-8">
|
||||
<p className="text-[9px] font-bold tracking-[0.18em] text-slate-500 uppercase">
|
||||
For the patient
|
||||
</p>
|
||||
<p className="text-[9px] font-bold tracking-[0.18em] text-teal-700 uppercase">
|
||||
For Walgreens
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-1">
|
||||
{payoffs.map((row) => (
|
||||
<div
|
||||
key={row.bizTitle}
|
||||
className="grid grid-cols-[0.92fr_1.08fr] gap-8 border-t border-slate-200 py-3"
|
||||
>
|
||||
<div>
|
||||
<h3 className="font-serif text-[14px] leading-[1.2] font-semibold text-slate-950">
|
||||
{row.patientTitle}
|
||||
</h3>
|
||||
<p className="mt-1 text-[9.5px] leading-[1.45] text-slate-600">
|
||||
{row.patient}
|
||||
</p>
|
||||
</div>
|
||||
<div className="border-l border-teal-600/30 pl-8">
|
||||
<h3 className="font-serif text-[14px] leading-[1.2] font-semibold text-teal-900">
|
||||
{row.bizTitle}
|
||||
</h3>
|
||||
<p className="mt-1 text-[9.5px] leading-[1.45] text-slate-600">
|
||||
{row.biz}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p className="mt-5 bg-teal-50/70 px-5 py-3 text-[10px] leading-[1.5] text-slate-700">
|
||||
<span className="font-semibold text-slate-900">
|
||||
It sits beside the Walgreens stack, not on top of it.{" "}
|
||||
</span>
|
||||
Dispensing, myWalgreens, and the Walgreens app keep doing their jobs.
|
||||
Medscribe is the patient-owned layer that feeds value back in with
|
||||
consent. A partnership unlocks co-branding, refill routing, and
|
||||
adherence reporting against Star and EQuIPP measures.
|
||||
</p>
|
||||
|
||||
<section className="mt-6 grid grid-cols-[1.12fr_0.88fr] items-start gap-9 border-t border-slate-300 pt-5">
|
||||
<div>
|
||||
<p className="northwell-print-label">The ask</p>
|
||||
<h3 className="mt-3 font-serif text-[20px] leading-[1.22] font-semibold text-slate-950">
|
||||
A short conversation about a record that moves adherence and keeps
|
||||
refills at Walgreens.
|
||||
</h3>
|
||||
<p className="mt-2 text-[10.5px] leading-[1.5] text-slate-600">
|
||||
Live in beta on iOS and Android today. The core patient experience
|
||||
ships now; the business layer is what a partnership builds together.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="northwell-print-label">Who is behind it</p>
|
||||
<h3 className="mt-3 font-serif text-[19px] font-semibold text-slate-950">
|
||||
Sean O'Connor
|
||||
</h3>
|
||||
<p className="mt-3 text-[10.5px] leading-[1.5] text-slate-600">
|
||||
A Bucknell computer science and engineering graduate who built
|
||||
Medscribe after watching his family lose the full picture of his
|
||||
grandfather's care.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</PrintPage>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
export type ClaimStatus = "built" | "beta-verified" | "roadmap";
|
||||
|
||||
export type ProductClaim = {
|
||||
publicCopy: string;
|
||||
status: ClaimStatus;
|
||||
evidence: string;
|
||||
verifiedOn: string;
|
||||
};
|
||||
|
||||
export const productRelease = {
|
||||
stage: "Internal beta",
|
||||
platforms: "iOS and Android",
|
||||
verifiedOn: "August 20, 2026",
|
||||
drugFacts: 203,
|
||||
rxNormTerms: 33_185,
|
||||
interactionPairs: 1_150,
|
||||
flaggedForReview: 76,
|
||||
} as const;
|
||||
|
||||
export const productClaims = {
|
||||
localAi: {
|
||||
publicCopy:
|
||||
"Visit transcription and supported AI tasks run on the device, without cloud AI inference.",
|
||||
status: "built",
|
||||
evidence: "App architecture and release-path audit",
|
||||
verifiedOn: "2026-08-20",
|
||||
},
|
||||
localRecord: {
|
||||
publicCopy:
|
||||
"The primary record is stored locally on the person's device, with no Medscribe cloud account.",
|
||||
status: "built",
|
||||
evidence: "Local SQLite data architecture",
|
||||
verifiedOn: "2026-08-20",
|
||||
},
|
||||
medicationFacts: {
|
||||
publicCopy:
|
||||
"Medication facts and known interaction pairs are read from bundled reference data rather than model memory.",
|
||||
status: "built",
|
||||
evidence: "Bundled data release manifest and deterministic answer paths",
|
||||
verifiedOn: "2026-08-20",
|
||||
},
|
||||
caregiverSharing: {
|
||||
publicCopy:
|
||||
"A person can manually export an encrypted snapshot targeted to a caregiver's public key.",
|
||||
status: "built",
|
||||
evidence:
|
||||
"QR pairing, safety-number display, encrypted export, ciphertext integrity, and wrong-recipient rejection",
|
||||
verifiedOn: "2026-08-20",
|
||||
},
|
||||
recordFreshness: {
|
||||
publicCopy:
|
||||
"Medication schedules can carry source, confirmation, timezone, and freshness state in the beta.",
|
||||
status: "built",
|
||||
evidence: "Medication schedule and Know Me freshness implementation",
|
||||
verifiedOn: "2026-08-20",
|
||||
},
|
||||
broaderProvenance: {
|
||||
publicCopy:
|
||||
"Source and freshness for conditions, allergies, baseline, and visit-derived changes are planned next.",
|
||||
status: "roadmap",
|
||||
evidence: "Current product roadmap",
|
||||
verifiedOn: "2026-08-20",
|
||||
},
|
||||
} satisfies Record<string, ProductClaim>;
|
||||
|
||||
export const publicRoutes = [
|
||||
"/",
|
||||
"/how-it-works",
|
||||
"/privacy",
|
||||
"/beta",
|
||||
] as const;
|
||||
@@ -1,15 +1,15 @@
|
||||
/** Shared beta / early-access copy for the marketing site. */
|
||||
|
||||
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 =
|
||||
"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 =
|
||||
"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 =
|
||||
"Beta on iOS (TestFlight) and Android (Google Play internal testing).";
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@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;
|
||||
|
||||
/* Light palette: mirrors the app's `lightColors` (constants/colors.ts). */
|
||||
@@ -53,7 +54,11 @@
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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
|
||||
* 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(rgba(15, 118, 110, 0.07) 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 {
|
||||
@page {
|
||||
margin: 0.25in;
|
||||
size: letter;
|
||||
:root {
|
||||
color-scheme: light !important;
|
||||
}
|
||||
|
||||
* {
|
||||
print-color-adjust: exact !important;
|
||||
-webkit-print-color-adjust: exact !important;
|
||||
animation: none !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
background: #ffffff !important;
|
||||
print-color-adjust: exact;
|
||||
-webkit-print-color-adjust: exact;
|
||||
}
|
||||
|
||||
body {
|
||||
width: 8.5in !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
color: #0f172a !important;
|
||||
background: #ffffff !important;
|
||||
}
|
||||
|
||||
.print-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.print-page {
|
||||
max-width: none !important;
|
||||
padding: 0 !important;
|
||||
.northwell-print-only {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.print-page * {
|
||||
box-shadow: none !important;
|
||||
animation: none !important;
|
||||
.northwell-print-page {
|
||||
position: relative;
|
||||
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 {
|
||||
print-color-adjust: exact;
|
||||
-webkit-print-color-adjust: exact;
|
||||
.northwell-print-page:last-child {
|
||||
break-after: auto;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||