Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c17689368 | ||
|
|
1a76a63ae3 | ||
|
|
0b0d149fba | ||
|
|
47acac8435 | ||
|
|
e9b5564127 | ||
|
|
f6aadc5107 | ||
|
|
d2a729b7b9 | ||
|
|
bd3b8daf5a | ||
|
|
33d4f19bf0 | ||
|
|
dcc1fff784 | ||
|
|
4238f6ab0e | ||
|
|
a8b7504a15 | ||
|
|
b41f2c643f | ||
|
|
5fd5011a9c | ||
|
|
b52eff02e1 | ||
|
|
ec89696c48 | ||
|
|
8f656e3808 | ||
|
|
af7f1b79e9 | ||
|
|
0d371313f9 | ||
|
|
0a353afeb7 | ||
|
|
e466b25837 | ||
|
|
81109f36ea | ||
|
|
d3c30859b2 | ||
|
|
2f8dcd66ad | ||
|
|
a590a506f8 | ||
|
|
86544c260a | ||
|
|
c70b50b867 | ||
|
|
7e1f1d6388 | ||
|
|
bbdef0faa6 | ||
|
|
d6040e2a09 | ||
|
|
d90d2ad9eb | ||
|
|
572d8eaa94 | ||
|
|
dcb70f9188 | ||
|
|
03cf4f9a86 | ||
|
|
a7a5174c7f |
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"version": "0.0.1",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "medscribe-web",
|
||||||
|
"runtimeExecutable": "npm",
|
||||||
|
"runtimeArgs": ["run", "dev"],
|
||||||
|
"port": 3000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -13,6 +13,11 @@
|
|||||||
# SERVERVAR="foo"
|
# SERVERVAR="foo"
|
||||||
# NEXT_PUBLIC_CLIENTVAR="bar"
|
# NEXT_PUBLIC_CLIENTVAR="bar"
|
||||||
|
|
||||||
# Password required to view the site (login page at /login).
|
# Optional password gate for private previews. The gate is off unless both
|
||||||
# Leave empty/unset to disable the gate.
|
# values are set, so a stale password alone cannot hide the public site.
|
||||||
|
SITE_PASSWORD_ENABLED="false"
|
||||||
SITE_PASSWORD=""
|
SITE_PASSWORD=""
|
||||||
|
|
||||||
|
# Umami analytics (optional). Omit NEXT_PUBLIC_UMAMI_WEBSITE_ID to disable tracking.
|
||||||
|
# NEXT_PUBLIC_UMAMI_WEBSITE_ID="your-website-uuid"
|
||||||
|
# NEXT_PUBLIC_UMAMI_SCRIPT_URL="https://cloud.umami.is/script.js"
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ next-env.d.ts
|
|||||||
|
|
||||||
# production
|
# production
|
||||||
/build
|
/build
|
||||||
|
/output/
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
# Medscribe Web
|
# Medscribe Web
|
||||||
|
|
||||||
Marketing homepage for [Medscribe](../README.md) — a privacy-first, on-device medical companion for iOS.
|
Public product website for [Medscribe](../README.md) — a private,
|
||||||
|
patient-controlled care companion for 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).
|
||||||
|
|
||||||
Uses **bun** exclusively (`bun.lock`).
|
Uses **bun** exclusively (`bun.lock`).
|
||||||
|
|
||||||
@@ -15,6 +17,47 @@ bun run dev
|
|||||||
|
|
||||||
Open [http://localhost:3000](http://localhost:3000).
|
Open [http://localhost:3000](http://localhost:3000).
|
||||||
|
|
||||||
|
## Routes
|
||||||
|
|
||||||
|
Public routes include:
|
||||||
|
|
||||||
|
- `/` — consumer-first portable-context story, including Questions
|
||||||
|
- `/how-it-works` — review loop, medication maintenance, Questions, and handoff
|
||||||
|
- `/privacy` — plain-language privacy and patient-control commitments
|
||||||
|
- `/access` — health-data-free access request
|
||||||
|
- `/medscribe-corporate-partner-overview.pdf` — public eight-slide partner deck
|
||||||
|
|
||||||
|
Named partner print material remains isolated and excluded from search:
|
||||||
|
|
||||||
|
- `/briefs/northwell`
|
||||||
|
- `/cvs`
|
||||||
|
- `/walgreens`
|
||||||
|
|
||||||
|
Legacy routes redirect to the current public experience and are excluded from
|
||||||
|
search.
|
||||||
|
|
||||||
|
### Analytics (optional)
|
||||||
|
|
||||||
|
Set in `.env` (see `.env.example`):
|
||||||
|
|
||||||
|
- `NEXT_PUBLIC_UMAMI_WEBSITE_ID` — website UUID from your Umami dashboard
|
||||||
|
- `NEXT_PUBLIC_UMAMI_SCRIPT_URL` — optional; defaults to `https://cloud.umami.is/script.js` (use your self-hosted URL if needed)
|
||||||
|
|
||||||
|
Leave the website ID unset to disable tracking (default for local dev).
|
||||||
|
|
||||||
|
### Optional preview password
|
||||||
|
|
||||||
|
The public site is open by default. To enable the private-preview gate, set
|
||||||
|
both values in the deployment environment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SITE_PASSWORD_ENABLED="true"
|
||||||
|
SITE_PASSWORD="choose-a-strong-password"
|
||||||
|
```
|
||||||
|
|
||||||
|
Unset `SITE_PASSWORD_ENABLED` or set it to `false` to keep the site public. A
|
||||||
|
stored password alone does not enable the gate.
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -22,14 +65,29 @@ bun run build
|
|||||||
bun run start
|
bun run start
|
||||||
```
|
```
|
||||||
|
|
||||||
Screenshots live in `public/screenshots/`. They are wrapped in an iPhone-style frame via [`react-device-mockup`](https://github.com/jung-youngmin/react-device-mockup).
|
Screenshots live in `public/screenshots/`. Every route uses the same fresh,
|
||||||
|
precomposited iPhone 17 Pro assets in
|
||||||
|
`public/screenshots/product-bezel-2026-08-24-unified-type/`; there are no raw or
|
||||||
|
CSS-framed product captures in the rendered site. The neutral Silver bezel and
|
||||||
|
the versioned path keep the imagery consistent and prevent stale optimizer
|
||||||
|
caches from hiding a new capture set.
|
||||||
|
Public pages label product imagery as an illustrative profile and Medscribe
|
||||||
|
interface.
|
||||||
|
|
||||||
### Capturing raw screenshots for the site
|
### Capturing raw screenshots for the site
|
||||||
|
|
||||||
Export **in-app content only** from the simulator or device — no simulator chrome or macOS capture toolbar. A normal in-app iOS status bar/tab bar is fine.
|
Export **in-app content only** from the simulator or device — no simulator chrome or macOS capture toolbar. A normal in-app status bar/tab bar is fine.
|
||||||
|
|
||||||
1. Run the app on **iPhone 17 Pro** (or 16 Pro) simulator or device.
|
1. Run the app on an **iPhone 17 Pro** simulator or device.
|
||||||
2. Capture each screen (⌘S in Simulator saves to Desktop).
|
2. Capture each screen (⌘S in Simulator saves to Desktop).
|
||||||
3. Drop PNGs into `public/screenshots/` and update paths in `src/app/page.tsx` / `src/components/feature-showcase.tsx`.
|
3. Place each 1206 × 2622 capture at native size in the 1350 × 2760 Silver
|
||||||
|
bezel at offset `+72+69`, clip it to the calibrated display-opening mask,
|
||||||
|
and export a transparent, lossless WebP into the shared versioned asset
|
||||||
|
directory referenced by the routes.
|
||||||
|
|
||||||
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.
|
Product images are composited at the bezel source size of 1350 × 2760 with
|
||||||
|
transparent backgrounds and rendered responsively through
|
||||||
|
`src/components/product-device.tsx`; the smaller `PhoneMockup` wrapper reuses
|
||||||
|
that same component. The homepage uses CSS view timelines for
|
||||||
|
scroll-scrubbed device and chapter motion on desktop and mobile, with a static
|
||||||
|
fallback and a reduced-motion override.
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
|
"puppeteer-core": "^25.4.0",
|
||||||
"tailwindcss": "^4.0.15",
|
"tailwindcss": "^4.0.15",
|
||||||
"typescript": "^5.8.2",
|
"typescript": "^5.8.2",
|
||||||
"typescript-eslint": "^8.27.0",
|
"typescript-eslint": "^8.27.0",
|
||||||
@@ -221,6 +222,8 @@
|
|||||||
|
|
||||||
"@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="],
|
"@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="],
|
||||||
|
|
||||||
|
"@puppeteer/browsers": ["@puppeteer/browsers@3.0.6", "", { "dependencies": { "modern-tar": "^0.7.6", "yargs": "^18.0.0" }, "peerDependencies": { "proxy-agent": ">=8.0.1", "yauzl": "^2.10.0 || ^3.4.0" }, "optionalPeers": ["proxy-agent", "yauzl"], "bin": { "browsers": "lib/main-cli.js" } }, "sha512-B/gKoqlFkzhvzsI6jo9K1cZz9o5ypviVv/xu8CwA4grZzyVwN+XfkT+tu8T1zrauuEXv6VhS2oGX+6NL95WcKA=="],
|
||||||
|
|
||||||
"@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA=="],
|
"@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA=="],
|
||||||
|
|
||||||
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.3.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA=="],
|
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.3.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA=="],
|
||||||
@@ -349,6 +352,8 @@
|
|||||||
|
|
||||||
"ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
|
"ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
|
||||||
|
|
||||||
|
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
||||||
|
|
||||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||||
|
|
||||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||||
@@ -403,10 +408,14 @@
|
|||||||
|
|
||||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||||
|
|
||||||
|
"chromium-bidi": ["chromium-bidi@17.0.2", "", { "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-5v9GQFhTktFvotn/OFNJBmKLKRAb6n9r0bVCwf7sHgWc3/JryK0bj1nn93L3pHFrfgcsu6Be6EWsDi+1XHTGDg=="],
|
||||||
|
|
||||||
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
|
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
|
||||||
|
|
||||||
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
|
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
|
||||||
|
|
||||||
|
"cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="],
|
||||||
|
|
||||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||||
|
|
||||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||||
@@ -437,6 +446,8 @@
|
|||||||
|
|
||||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||||
|
|
||||||
|
"devtools-protocol": ["devtools-protocol@0.0.1653615", "", {}, "sha512-pGVkY3T/qXxAp2nFPodwYqOevk6ncNMSmvL8QfRCx5ZWGd6Vor7AFNmyaA8Zs6uJyP1QAfjuLandCgvSix1BNA=="],
|
||||||
|
|
||||||
"doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
|
"doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
|
||||||
|
|
||||||
"drizzle-kit": ["drizzle-kit@0.31.10", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="],
|
"drizzle-kit": ["drizzle-kit@0.31.10", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="],
|
||||||
@@ -469,6 +480,8 @@
|
|||||||
|
|
||||||
"esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
|
"esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
|
||||||
|
|
||||||
|
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||||
|
|
||||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||||
|
|
||||||
"eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "bin": "bin/eslint.js" }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="],
|
"eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "bin": "bin/eslint.js" }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="],
|
||||||
@@ -537,6 +550,10 @@
|
|||||||
|
|
||||||
"generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="],
|
"generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="],
|
||||||
|
|
||||||
|
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||||
|
|
||||||
|
"get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="],
|
||||||
|
|
||||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||||
|
|
||||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||||
@@ -705,6 +722,10 @@
|
|||||||
|
|
||||||
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||||
|
|
||||||
|
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
|
||||||
|
|
||||||
|
"modern-tar": ["modern-tar@0.7.7", "", {}, "sha512-t9VmxaqrmANnEOBhpSDI6HD192Ge48k8vmWqQQL7hSFEqHEYwZbbsu49+aKLWZeRvFs3j1pMhXOqqF4kPlvjkQ=="],
|
||||||
|
|
||||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
|
|
||||||
"nanoid": ["nanoid@3.3.15", "", { "bin": "bin/nanoid.cjs" }, "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA=="],
|
"nanoid": ["nanoid@3.3.15", "", { "bin": "bin/nanoid.cjs" }, "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA=="],
|
||||||
@@ -767,6 +788,8 @@
|
|||||||
|
|
||||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||||
|
|
||||||
|
"puppeteer-core": ["puppeteer-core@25.4.0", "", { "dependencies": { "@puppeteer/browsers": "3.0.6", "chromium-bidi": "17.0.2", "devtools-protocol": "0.0.1653615", "typed-query-selector": "^2.12.2", "webdriver-bidi-protocol": "0.4.2", "ws": "^8.21.1" } }, "sha512-K1plkLOdeoUnGeT1OvdqF3qxl33v+Ra/uH5VyPEhXdMcpvGiEskHzxxEU3fgpccJpJLIipB/rPUsvkZRWeKqOA=="],
|
||||||
|
|
||||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||||
|
|
||||||
"react": ["react@19.2.7", "", {}, "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ=="],
|
"react": ["react@19.2.7", "", {}, "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ=="],
|
||||||
@@ -831,6 +854,8 @@
|
|||||||
|
|
||||||
"stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
|
"stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
|
||||||
|
|
||||||
|
"string-width": ["string-width@8.2.2", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg=="],
|
||||||
|
|
||||||
"string.prototype.includes": ["string.prototype.includes@2.0.1", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3" } }, "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg=="],
|
"string.prototype.includes": ["string.prototype.includes@2.0.1", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3" } }, "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg=="],
|
||||||
|
|
||||||
"string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="],
|
"string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="],
|
||||||
@@ -843,6 +868,8 @@
|
|||||||
|
|
||||||
"string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="],
|
"string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="],
|
||||||
|
|
||||||
|
"strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
|
||||||
|
|
||||||
"strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
|
"strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
|
||||||
|
|
||||||
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||||
@@ -881,6 +908,8 @@
|
|||||||
|
|
||||||
"typed-array-length": ["typed-array-length@1.0.8", "", { "dependencies": { "call-bind": "^1.0.9", "for-each": "^0.3.5", "gopd": "^1.2.0", "is-typed-array": "^1.1.15", "possible-typed-array-names": "^1.1.0", "reflect.getprototypeof": "^1.0.10" } }, "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g=="],
|
"typed-array-length": ["typed-array-length@1.0.8", "", { "dependencies": { "call-bind": "^1.0.9", "for-each": "^0.3.5", "gopd": "^1.2.0", "is-typed-array": "^1.1.15", "possible-typed-array-names": "^1.1.0", "reflect.getprototypeof": "^1.0.10" } }, "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g=="],
|
||||||
|
|
||||||
|
"typed-query-selector": ["typed-query-selector@2.12.2", "", {}, "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ=="],
|
||||||
|
|
||||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||||
|
|
||||||
"typescript-eslint": ["typescript-eslint@8.62.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.62.0", "@typescript-eslint/parser": "8.62.0", "@typescript-eslint/typescript-estree": "8.62.0", "@typescript-eslint/utils": "8.62.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8QxXi+ZACKX0kaqO4gY8kn0RSD9gFfaHDWwjqtEN48aWCBkX4MJaufWN+c3BzlrXLOxfywDL8CaoqUwcRq4j4Q=="],
|
"typescript-eslint": ["typescript-eslint@8.62.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.62.0", "@typescript-eslint/parser": "8.62.0", "@typescript-eslint/typescript-estree": "8.62.0", "@typescript-eslint/utils": "8.62.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8QxXi+ZACKX0kaqO4gY8kn0RSD9gFfaHDWwjqtEN48aWCBkX4MJaufWN+c3BzlrXLOxfywDL8CaoqUwcRq4j4Q=="],
|
||||||
@@ -893,6 +922,8 @@
|
|||||||
|
|
||||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||||
|
|
||||||
|
"webdriver-bidi-protocol": ["webdriver-bidi-protocol@0.4.2", "", {}, "sha512-VSV+fzfChirL3e7jay2yUC7B4HQCGtEWEg/MSSQbK+qWbqeGlRLlXTzPpYr3XGUvbpDHumWZBJxgesg4N7dbtA=="],
|
||||||
|
|
||||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||||
|
|
||||||
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
|
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
|
||||||
@@ -905,6 +936,16 @@
|
|||||||
|
|
||||||
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||||
|
|
||||||
|
"wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
|
||||||
|
|
||||||
|
"ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="],
|
||||||
|
|
||||||
|
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||||
|
|
||||||
|
"yargs": ["yargs@18.1.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^8.2.1", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg=="],
|
||||||
|
|
||||||
|
"yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
|
||||||
|
|
||||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||||
|
|
||||||
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||||
@@ -923,6 +964,8 @@
|
|||||||
|
|
||||||
"@unrs/resolver-binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
|
"@unrs/resolver-binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
|
||||||
|
|
||||||
|
"cliui/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||||
|
|
||||||
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||||
|
|
||||||
"eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
"eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||||
@@ -941,6 +984,10 @@
|
|||||||
|
|
||||||
"tsx/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
|
"tsx/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
|
||||||
|
|
||||||
|
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||||
|
|
||||||
|
"wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||||
|
|
||||||
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
|
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
|
||||||
|
|
||||||
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="],
|
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="],
|
||||||
@@ -987,6 +1034,8 @@
|
|||||||
|
|
||||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
|
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
|
||||||
|
|
||||||
|
"cliui/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
|
"tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="],
|
"tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="],
|
||||||
@@ -1039,6 +1088,8 @@
|
|||||||
|
|
||||||
"tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
|
"tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
|
||||||
|
|
||||||
|
"wrap-ansi/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
"@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
import "./src/env.js";
|
import "./src/env.js";
|
||||||
|
|
||||||
/** @type {import("next").NextConfig} */
|
/** @type {import("next").NextConfig} */
|
||||||
const config = {};
|
const config = {
|
||||||
|
images: {
|
||||||
|
qualities: [75, 92],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
|
"puppeteer-core": "^25.4.0",
|
||||||
"tailwindcss": "^4.0.15",
|
"tailwindcss": "^4.0.15",
|
||||||
"typescript": "^5.8.2",
|
"typescript": "^5.8.2",
|
||||||
"typescript-eslint": "^8.27.0"
|
"typescript-eslint": "^8.27.0"
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="2550" height="3300" viewBox="0 0 850 1100" preserveAspectRatio="xMidYMin slice">
|
||||||
|
<defs>
|
||||||
|
<pattern id="grid" width="28" height="28" patternUnits="userSpaceOnUse">
|
||||||
|
<path d="M 28 0 L 0 0 0 28" fill="none" stroke="#0f172a" stroke-opacity="0.055" stroke-width="1"/>
|
||||||
|
</pattern>
|
||||||
|
<linearGradient id="wash" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0%" stop-color="#ccfbf1" stop-opacity="0.5"/>
|
||||||
|
<stop offset="100%" stop-color="#ccfbf1" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="850" height="1100" fill="#f8fafc"/>
|
||||||
|
<rect width="850" height="1100" fill="url(#grid)"/>
|
||||||
|
<rect width="850" height="260" fill="url(#wash)"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 722 B |
|
Before Width: | Height: | Size: 295 KiB |
|
Before Width: | Height: | Size: 348 KiB |
|
Before Width: | Height: | Size: 454 KiB |
|
Before Width: | Height: | Size: 226 KiB |
|
Before Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 260 KiB |
|
Before Width: | Height: | Size: 296 KiB |
|
Before Width: | Height: | Size: 668 KiB |
|
Before Width: | Height: | Size: 186 KiB |
|
Before Width: | Height: | Size: 187 KiB |
|
Before Width: | Height: | Size: 336 KiB |
|
Before Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 192 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
After Width: | Height: | Size: 196 KiB |
|
After Width: | Height: | Size: 229 KiB |
|
After Width: | Height: | Size: 162 KiB |
|
After Width: | Height: | Size: 178 KiB |
|
After Width: | Height: | Size: 150 KiB |
|
After Width: | Height: | Size: 158 KiB |
|
After Width: | Height: | Size: 180 KiB |
|
After Width: | Height: | Size: 193 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 226 KiB |
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 202 KiB |
|
After Width: | Height: | Size: 221 KiB |
|
After Width: | Height: | Size: 178 KiB |
|
After Width: | Height: | Size: 192 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 795 KiB |
|
After Width: | Height: | Size: 887 KiB |
|
After Width: | Height: | Size: 839 KiB |
|
After Width: | Height: | Size: 899 KiB |
|
After Width: | Height: | Size: 796 KiB |
|
After Width: | Height: | Size: 916 KiB |
|
After Width: | Height: | Size: 736 KiB |
|
After Width: | Height: | Size: 854 KiB |
|
After Width: | Height: | Size: 265 KiB |
|
After Width: | Height: | Size: 268 KiB |
|
After Width: | Height: | Size: 173 KiB |
|
After Width: | Height: | Size: 179 KiB |
|
After Width: | Height: | Size: 733 KiB |
|
After Width: | Height: | Size: 820 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 897 KiB |
|
After Width: | Height: | Size: 987 KiB |
|
After Width: | Height: | Size: 878 KiB |
|
After Width: | Height: | Size: 937 KiB |
|
After Width: | Height: | Size: 833 KiB |
|
After Width: | Height: | Size: 952 KiB |
|
After Width: | Height: | Size: 833 KiB |
|
After Width: | Height: | Size: 952 KiB |
|
After Width: | Height: | Size: 698 KiB |
|
After Width: | Height: | Size: 787 KiB |
|
After Width: | Height: | Size: 326 KiB |
|
After Width: | Height: | Size: 331 KiB |
|
After Width: | Height: | Size: 293 KiB |
|
After Width: | Height: | Size: 301 KiB |
|
After Width: | Height: | Size: 768 KiB |
|
After Width: | Height: | Size: 861 KiB |
|
Before Width: | Height: | Size: 485 KiB |
|
Before Width: | Height: | Size: 198 KiB |
|
Before Width: | Height: | Size: 234 KiB |
|
Before Width: | Height: | Size: 395 KiB |
|
Before Width: | Height: | Size: 217 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,178 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import {
|
||||||
|
ArrowRight,
|
||||||
|
CalendarDays,
|
||||||
|
Check,
|
||||||
|
ContactRound,
|
||||||
|
Mail,
|
||||||
|
Pill,
|
||||||
|
Smartphone,
|
||||||
|
Users,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
import { SectionLabel } from "~/components/section-label";
|
||||||
|
import { Badge } from "~/components/ui/badge";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
import { productRelease } from "~/content/product";
|
||||||
|
import { ACCESS_INVITE_MAILTO } from "~/lib/access";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Request access",
|
||||||
|
description:
|
||||||
|
"Request access to Medscribe's closed beta without including personal health information.",
|
||||||
|
};
|
||||||
|
|
||||||
|
const fitCards = [
|
||||||
|
{
|
||||||
|
icon: Smartphone,
|
||||||
|
title: "Several moving pieces",
|
||||||
|
body: "You coordinate visits, medications, reminders, or follow-ups across everyday life.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Users,
|
||||||
|
title: "A trusted care circle",
|
||||||
|
body: "You want an easier way to keep a family caregiver informed when you choose.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Check,
|
||||||
|
title: "Clear, reviewable context",
|
||||||
|
body: "You value a record you can read, check, and bring into the next conversation.",
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const productCards = [
|
||||||
|
{
|
||||||
|
icon: CalendarDays,
|
||||||
|
title: "Visits that stay useful",
|
||||||
|
body: "Keep notes, transcripts, summaries, and follow-ups together so the details remain easy to revisit.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Pill,
|
||||||
|
title: "Medication routines at a glance",
|
||||||
|
body: "See medications, schedules, reminders, and refill details in one calm, readable place.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: ContactRound,
|
||||||
|
title: "A ready-to-share picture",
|
||||||
|
body: "Bring selected medications, allergies, conditions, contacts, and preferences together in Know Me.",
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export default function AccessPage() {
|
||||||
|
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-display text-5xl leading-[1.02] font-semibold tracking-[-0.03em] sm:text-6xl">
|
||||||
|
Bring your care context together.
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground mt-6 max-w-2xl text-lg leading-relaxed sm:text-xl">
|
||||||
|
Medscribe is made for people managing repeated visits, several
|
||||||
|
medications, and the trusted family members who help them stay
|
||||||
|
organized.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="surface-panel rounded-[2rem] p-7 sm:p-9">
|
||||||
|
<div className="bg-primary-light text-primary flex size-12 items-center justify-center rounded-xl">
|
||||||
|
<Mail className="size-6" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-foreground mt-5 font-display text-3xl font-semibold">
|
||||||
|
Request Medscribe access
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-4 text-sm leading-relaxed">
|
||||||
|
The beta is currently closed to a small group. Tell us your
|
||||||
|
platform and whether you are interested as a patient, caregiver,
|
||||||
|
or both, and we'll follow up as access becomes available.
|
||||||
|
</p>
|
||||||
|
<Button asChild size="lg" className="mt-7 w-full sm:w-auto">
|
||||||
|
<a href={ACCESS_INVITE_MAILTO}>
|
||||||
|
Draft access 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">
|
||||||
|
Please do not send diagnoses, medication names, visit notes, or
|
||||||
|
other health details. Email is not a medical-support or emergency
|
||||||
|
channel.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="section-wash px-4 py-20 sm:px-6 lg:py-24">
|
||||||
|
<div className="mx-auto max-w-6xl">
|
||||||
|
<SectionLabel>Made for real life</SectionLabel>
|
||||||
|
<h2 className="text-foreground mt-4 max-w-3xl font-display text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||||
|
Keep the details close without carrying the whole burden in your
|
||||||
|
head.
|
||||||
|
</h2>
|
||||||
|
<div className="mt-10 grid gap-4 md:grid-cols-3">
|
||||||
|
{fitCards.map((item) => {
|
||||||
|
const Icon = item.icon;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
key={item.title}
|
||||||
|
className="surface-panel rounded-2xl p-6"
|
||||||
|
>
|
||||||
|
<Icon className="text-primary size-6" aria-hidden="true" />
|
||||||
|
<h3 className="text-foreground mt-5 font-display 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">
|
||||||
|
<SectionLabel>One connected picture</SectionLabel>
|
||||||
|
<h2 className="text-foreground mt-4 max-w-3xl font-display text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||||
|
Visits, medications, and the people who matter—together.
|
||||||
|
</h2>
|
||||||
|
<div className="mt-10 grid gap-4 md:grid-cols-3">
|
||||||
|
{productCards.map((item) => {
|
||||||
|
const Icon = item.icon;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
key={item.title}
|
||||||
|
className="surface-panel rounded-2xl p-6"
|
||||||
|
>
|
||||||
|
<Icon className="text-primary size-6" aria-hidden="true" />
|
||||||
|
<h3 className="text-foreground mt-5 font-display 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="night-card mt-14 rounded-[2rem] p-7 text-white sm:p-10">
|
||||||
|
<p className="text-xs font-bold tracking-[0.14em] text-teal-100 uppercase">
|
||||||
|
Keep your care team in the loop
|
||||||
|
</p>
|
||||||
|
<h2 className="mt-4 max-w-3xl font-display text-3xl font-semibold sm:text-4xl">
|
||||||
|
Medscribe helps you prepare for better conversations. Medical
|
||||||
|
decisions remain with you and your qualified care team.
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
export default function LegacyAccessPage() {
|
||||||
|
redirect("/access");
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
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-display 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 page is formatted as a 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-display 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,333 +1,11 @@
|
|||||||
import type { CSSProperties } from "react";
|
|
||||||
|
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import Image from "next/image";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
import { MedscribeLogo } from "~/components/medscribe-logo";
|
|
||||||
import { PhoneMockup } from "~/components/phone-mockup";
|
|
||||||
import { PrintButton } from "~/components/print-button";
|
|
||||||
import { SectionLabel } from "~/components/section-label";
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "~/components/ui/card";
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Executive Summary",
|
title: "Medscribe",
|
||||||
description:
|
robots: { index: false, follow: false, nocache: true },
|
||||||
"One-page executive summary for Medscribe, a private medical context app for families managing complex care.",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const TAGLINE =
|
|
||||||
"Your medical truth, in your pocket, and only where you say it goes.";
|
|
||||||
|
|
||||||
const SNAPSHOT =
|
|
||||||
"Medscribe is a private, emergency-ready medical record for families managing complex care. Patients and caregivers keep medications, visits, timing, and baseline current day to day, so a clinician or provider can be handed exactly what matters when the system does not know the patient.";
|
|
||||||
|
|
||||||
const PROBLEM =
|
|
||||||
"Someone managing a serious condition ends up somewhere new: an ER outside their network, a covering doctor, a hospital that runs different software. Their records don't transfer. The team assesses them on how they present, not on their baseline, their exact medication timing, or what changed at their last visit. The family is there and they know, but they can't produce a clear, structured record under pressure.";
|
|
||||||
|
|
||||||
const PROBLEM_BROADER =
|
|
||||||
"This is not a rare edge case. It happens every day, to patients with Parkinson's, diabetes, heart disease, dementia, and a hundred other conditions that require precise, continuous management across a fragmented system. The failure is not that families don't care. The failure is that the patient's context does not travel with them.";
|
|
||||||
|
|
||||||
const DIFFERENTIATION =
|
|
||||||
"The market is full of AI visit recorders, medication trackers, health-record apps, and emergency QR cards, each owning one slice. None owns the whole job. In Medscribe, the daily tools are the maintenance system for one emergency-ready record.";
|
|
||||||
|
|
||||||
const differentiators = [
|
|
||||||
{
|
|
||||||
title: "The record is the product",
|
|
||||||
body: "Recording, scanning, reminders, and Q&A keep one private, emergency-ready record accurate. Competitors ship visit summaries; we ship a record you can hand over in a crisis.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Private by architecture",
|
|
||||||
body: "AI runs on-device: no cloud processing, no health-data upload, no tracking, no account. Others say \"encrypted\" while their store disclosures admit cloud collection.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Facts-first, not a guess",
|
|
||||||
body: "Medication answers render trusted drug facts deterministically, deferring dosing and diagnosis to a clinician or pharmacist. Built for complex-care families, not a general health bot.",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const scenarios = [
|
|
||||||
{
|
|
||||||
title: "The ER that doesn't know you",
|
|
||||||
body: "A patient on time-critical medication ends up at a hospital outside their network. Missing a dose by hours isn't an inconvenience; it's a medical event. Nobody has the schedule, and the family can't produce it under pressure.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "The caregiver handoff",
|
|
||||||
body: "An adult child manages a parent's medications, appointments, and history. When they're not there, the context disappears: a sibling visits, a home aide covers, a shift changes. Critical details live in one person's memory.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "The new diabetic, overwhelmed",
|
|
||||||
body: "A newly diagnosed patient leaves with insulin timing, diet changes, and follow-ups they half-remember. By the next visit, the provider can't tell what was understood, tried, or missed.",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const impact = [
|
|
||||||
{
|
|
||||||
title: "Fewer blank handoffs",
|
|
||||||
body: "When a caregiver or clinician takes over, the facts are already assembled: conditions, medications, allergies, baseline, contacts, and recent changes.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Safer medication moments",
|
|
||||||
body: "Time-critical schedules and dose history stay visible, so a missed or delayed medication is easier to catch before it becomes a crisis.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Patients and providers aligned",
|
|
||||||
body: "Families see what changed and why; providers get a current snapshot instead of reconstructing it from a stressed family's memory.",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const printLightTokens = {
|
|
||||||
"--color-foreground": "#0f172a",
|
|
||||||
"--color-primary": "#0f766e",
|
|
||||||
"--color-surface": "#ffffff",
|
|
||||||
"--color-border-soft": "#e2e8f0",
|
|
||||||
"--color-muted-foreground": "#475569",
|
|
||||||
color: "#0f172a",
|
|
||||||
} as CSSProperties;
|
|
||||||
|
|
||||||
export default function ExecutiveSummaryPage() {
|
export default function ExecutiveSummaryPage() {
|
||||||
return (
|
redirect("/");
|
||||||
<>
|
|
||||||
{/* ===================== SCREEN ===================== */}
|
|
||||||
<div className="print-hidden mx-auto max-w-6xl space-y-14 px-4 py-10 sm:px-6 lg:space-y-20 lg:py-14">
|
|
||||||
{/* Hero */}
|
|
||||||
<section className="grid items-start gap-8 lg:grid-cols-[minmax(0,1fr)_minmax(220px,280px)] lg:gap-14">
|
|
||||||
<div className="space-y-5">
|
|
||||||
<SectionLabel>Executive Summary · June 2026</SectionLabel>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<h1 className="font-serif text-5xl font-semibold tracking-tight text-foreground sm:text-6xl">
|
|
||||||
Medscribe
|
|
||||||
</h1>
|
|
||||||
<p className="font-serif text-xl font-semibold tracking-tight text-foreground sm:text-2xl">
|
|
||||||
{TAGLINE}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<p className="text-base leading-relaxed text-muted-foreground sm:text-lg">
|
|
||||||
{SNAPSHOT}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="justify-self-center lg:justify-self-end">
|
|
||||||
<PhoneMockup
|
|
||||||
src="/screenshots/home.png"
|
|
||||||
alt="Medscribe home screen"
|
|
||||||
width={200}
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* The Problem */}
|
|
||||||
<section className="space-y-5">
|
|
||||||
<SectionLabel>The problem</SectionLabel>
|
|
||||||
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
|
||||||
The patient's context does not travel with them.
|
|
||||||
</h2>
|
|
||||||
<div className="grid gap-6 leading-relaxed text-muted-foreground lg:grid-cols-2 lg:gap-10">
|
|
||||||
<p>{PROBLEM}</p>
|
|
||||||
<p className="font-medium text-foreground">{PROBLEM_BROADER}</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Scenarios */}
|
|
||||||
<section className="space-y-6">
|
|
||||||
<SectionLabel>Who this is for</SectionLabel>
|
|
||||||
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
|
||||||
The moments where context is everything.
|
|
||||||
</h2>
|
|
||||||
<div className="grid gap-4 md:grid-cols-3">
|
|
||||||
{scenarios.map((scenario) => (
|
|
||||||
<Card key={scenario.title} className="card-hover">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-lg">{scenario.title}</CardTitle>
|
|
||||||
<CardDescription>{scenario.body}</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Differentiation */}
|
|
||||||
<section className="space-y-6">
|
|
||||||
<SectionLabel>Why this is different</SectionLabel>
|
|
||||||
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
|
||||||
Everyone owns a slice. We own the whole job.
|
|
||||||
</h2>
|
|
||||||
<p className="max-w-4xl leading-relaxed text-muted-foreground">
|
|
||||||
{DIFFERENTIATION}
|
|
||||||
</p>
|
|
||||||
<div className="grid gap-4 md:grid-cols-3">
|
|
||||||
{differentiators.map((item) => (
|
|
||||||
<Card key={item.title} className="card-hover">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-lg">{item.title}</CardTitle>
|
|
||||||
<CardDescription>{item.body}</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Impact */}
|
|
||||||
<section className="space-y-6">
|
|
||||||
<SectionLabel>Potential impact</SectionLabel>
|
|
||||||
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
|
||||||
Less panic, more context, better handoffs.
|
|
||||||
</h2>
|
|
||||||
<div className="grid gap-4 md:grid-cols-3">
|
|
||||||
{impact.map((item) => (
|
|
||||||
<Card
|
|
||||||
key={item.title}
|
|
||||||
className="card-hover border-primary-button/20 bg-primary-button text-white"
|
|
||||||
>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-white">{item.title}</CardTitle>
|
|
||||||
<CardDescription className="text-white/75">
|
|
||||||
{item.body}
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Beta */}
|
|
||||||
<section className="flex flex-col gap-5 rounded-xl border border-border-soft bg-surface p-6 sm:flex-row sm:items-center sm:justify-between sm:p-8">
|
|
||||||
<p className="max-w-xl text-sm leading-relaxed text-muted-foreground">
|
|
||||||
A beta is available on iOS through TestFlight. Everything runs
|
|
||||||
privately on the device, so no health data leaves the phone. If
|
|
||||||
you'd like to try it, reach out.
|
|
||||||
</p>
|
|
||||||
<PrintButton size="lg" className="shrink-0" />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<p className="border-t border-border-soft pt-6 text-sm text-muted-foreground">
|
|
||||||
Sean O'Connor
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* ===================== PRINT: dense one-page PDF ===================== */}
|
|
||||||
<article
|
|
||||||
style={printLightTokens}
|
|
||||||
className="print-sheet hidden overflow-hidden print:flex print:h-[10.5in] print:flex-col print:justify-between print:px-[0.4in] print:py-[0.25in] print:text-[11pt] print:leading-[1.25]"
|
|
||||||
>
|
|
||||||
{/* Header */}
|
|
||||||
<header className="grid grid-cols-[minmax(0,1fr)_1.1in] items-stretch gap-3 border-b border-teal-900/10 pb-2">
|
|
||||||
<div className="flex flex-col justify-between py-0.5">
|
|
||||||
<p className="text-[11pt] font-semibold tracking-[0.15em] text-primary uppercase">
|
|
||||||
Executive Summary · June 2026
|
|
||||||
</p>
|
|
||||||
<div className="mt-2 flex items-center gap-2.5">
|
|
||||||
<MedscribeLogo className="h-[20pt] w-auto text-foreground" />
|
|
||||||
<h1 className="font-serif text-[22pt] leading-none font-semibold tracking-tight text-foreground">
|
|
||||||
Medscribe
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
<p className="mt-2 font-serif text-[12pt] leading-snug font-medium tracking-tight text-foreground italic">
|
|
||||||
{TAGLINE}
|
|
||||||
</p>
|
|
||||||
<p className="mt-3 text-[11pt] leading-[1.25] text-slate-700">
|
|
||||||
{SNAPSHOT}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<div className="h-[2in] w-[0.94in] overflow-hidden rounded-[11px] border-[3px] border-slate-950 bg-white shadow-[0_8px_18px_rgba(15,23,42,0.14)]">
|
|
||||||
<Image
|
|
||||||
src="/screenshots/home.png"
|
|
||||||
alt="Medscribe home screen"
|
|
||||||
width={1206}
|
|
||||||
height={2622}
|
|
||||||
priority
|
|
||||||
unoptimized
|
|
||||||
className="h-full w-full object-cover object-top"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{/* The Problem */}
|
|
||||||
<section className="border-b border-teal-900/10 py-1">
|
|
||||||
<h2 className="font-serif text-[12pt] font-semibold tracking-tight text-foreground">
|
|
||||||
The Problem
|
|
||||||
</h2>
|
|
||||||
<p className="mt-0.5 text-[11pt] leading-[1.25] text-slate-700">
|
|
||||||
{PROBLEM} {PROBLEM_BROADER}
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Who This Is For */}
|
|
||||||
<section className="border-b border-teal-900/10 py-1">
|
|
||||||
<h2 className="font-serif text-[12pt] font-semibold tracking-tight text-foreground">
|
|
||||||
Who This Is For
|
|
||||||
</h2>
|
|
||||||
<div className="mt-0.5 grid grid-cols-3 gap-x-4">
|
|
||||||
{scenarios.map((scenario) => (
|
|
||||||
<div key={scenario.title}>
|
|
||||||
<p className="text-[11pt] font-semibold leading-[1.25] text-foreground">
|
|
||||||
{scenario.title}
|
|
||||||
</p>
|
|
||||||
<p className="text-[11pt] leading-[1.25] text-slate-700">
|
|
||||||
{scenario.body}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Why This Is Different */}
|
|
||||||
<section className="border-b border-teal-900/10 py-1">
|
|
||||||
<h2 className="font-serif text-[12pt] font-semibold tracking-tight text-foreground">
|
|
||||||
Why This Is Different
|
|
||||||
</h2>
|
|
||||||
<p className="mt-0.5 text-[11pt] leading-[1.25] text-slate-700">
|
|
||||||
{DIFFERENTIATION}
|
|
||||||
</p>
|
|
||||||
<div className="mt-1 grid grid-cols-3 gap-x-4">
|
|
||||||
{differentiators.map((item) => (
|
|
||||||
<p
|
|
||||||
key={item.title}
|
|
||||||
className="text-[11pt] leading-[1.25] text-slate-700"
|
|
||||||
>
|
|
||||||
<span className="font-semibold text-foreground">
|
|
||||||
{item.title}.
|
|
||||||
</span>{" "}
|
|
||||||
{item.body}
|
|
||||||
</p>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Impact */}
|
|
||||||
<section className="py-1">
|
|
||||||
<h2 className="font-serif text-[12pt] font-semibold tracking-tight text-foreground">
|
|
||||||
Impact
|
|
||||||
</h2>
|
|
||||||
<div className="mt-0.5 grid grid-cols-3 gap-x-4">
|
|
||||||
{impact.map((item) => (
|
|
||||||
<p
|
|
||||||
key={item.title}
|
|
||||||
className="text-[11pt] leading-[1.25] text-slate-700"
|
|
||||||
>
|
|
||||||
<span className="font-semibold text-foreground">
|
|
||||||
{item.title}.
|
|
||||||
</span>{" "}
|
|
||||||
{item.body}
|
|
||||||
</p>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<footer className="flex items-center justify-between border-t border-teal-900/10 pt-1 text-[11pt] text-slate-500">
|
|
||||||
<p>Sean O'Connor</p>
|
|
||||||
<p className="text-slate-400">
|
|
||||||
A beta is available on iOS through TestFlight.
|
|
||||||
</p>
|
|
||||||
</footer>
|
|
||||||
</article>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,292 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import {
|
||||||
|
ArrowRight,
|
||||||
|
CalendarDays,
|
||||||
|
Check,
|
||||||
|
ListChecks,
|
||||||
|
MessageCircleQuestion,
|
||||||
|
RotateCcw,
|
||||||
|
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";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "How it works",
|
||||||
|
description:
|
||||||
|
"See how Medscribe brings visits, medication routines, and important context into one reviewable record.",
|
||||||
|
};
|
||||||
|
|
||||||
|
const dailyRhythm = [
|
||||||
|
{
|
||||||
|
icon: CalendarDays,
|
||||||
|
title: "Start with the moment",
|
||||||
|
body: "Give each visit a clear place so the details remain easy to find later.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: ListChecks,
|
||||||
|
title: "Review what matters",
|
||||||
|
body: "Check the useful details and keep your own judgment in the loop.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: MessageCircleQuestion,
|
||||||
|
title: "Keep the next question",
|
||||||
|
body: "Save what you want to ask while it is still fresh in your mind.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: RotateCcw,
|
||||||
|
title: "Return when needed",
|
||||||
|
body: "Revisit the same picture before the next appointment or handoff.",
|
||||||
|
},
|
||||||
|
] 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 decide what you should share or who should receive it.",
|
||||||
|
] 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">A day with Medscribe</Badge>
|
||||||
|
<h1 className="text-foreground mt-6 max-w-4xl font-display 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 clear. You stay in the review loop from the first note to
|
||||||
|
the next conversation.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="section-wash px-4 py-18 sm:px-6 lg:py-22">
|
||||||
|
<div className="mx-auto max-w-6xl">
|
||||||
|
<SectionLabel>Start with what matters</SectionLabel>
|
||||||
|
<h2 className="text-foreground mt-4 max-w-3xl font-display text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||||
|
A clear rhythm from the moment to the next conversation.
|
||||||
|
</h2>
|
||||||
|
<div className="mt-10 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
|
{dailyRhythm.map((item, index) => {
|
||||||
|
const Icon = item.icon;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
key={item.title}
|
||||||
|
className="surface-panel rounded-2xl 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-display 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="surface-panel mt-6 flex gap-3 rounded-2xl p-5">
|
||||||
|
<Check
|
||||||
|
className="text-primary mt-0.5 size-5 shrink-0"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<p className="text-foreground text-sm leading-relaxed">
|
||||||
|
You review the details before they become part of the picture you
|
||||||
|
carry.
|
||||||
|
</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-display text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||||
|
Keep the visit and the takeaway together.
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-5 leading-relaxed">
|
||||||
|
The visit remains available beside the summary so you can check
|
||||||
|
wording, follow-ups, and the details you want to remember.
|
||||||
|
</p>
|
||||||
|
<p className="surface-panel text-muted-foreground mt-5 rounded-2xl p-5 text-sm leading-relaxed">
|
||||||
|
<strong className="text-foreground">Stay oriented:</strong> move
|
||||||
|
between the transcript and summary whenever you want the fuller
|
||||||
|
context.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="justify-self-center py-2">
|
||||||
|
<PhoneMockup
|
||||||
|
src="/screenshots/product-bezel-2026-08-24-unified-type/summary-reviewed-natural.webp"
|
||||||
|
darkSrc="/screenshots/product-bezel-2026-08-24-unified-type/summary-reviewed-natural-dark.webp"
|
||||||
|
alt="Illustrative Medscribe visit summary"
|
||||||
|
width={280}
|
||||||
|
/>
|
||||||
|
<p className="text-muted-foreground mt-4 text-center text-xs">
|
||||||
|
Illustrative profile · Medscribe interface
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-12 lg:grid-cols-2 lg:items-center">
|
||||||
|
<div className="justify-self-center py-2 lg:order-1">
|
||||||
|
<PhoneMockup
|
||||||
|
src="/screenshots/product-bezel-2026-08-24-unified-type/medication-detail-natural.webp"
|
||||||
|
darkSrc="/screenshots/product-bezel-2026-08-24-unified-type/medication-detail-natural-dark.webp"
|
||||||
|
alt="Illustrative Medscribe medication detail"
|
||||||
|
width={280}
|
||||||
|
/>
|
||||||
|
<p className="text-muted-foreground mt-4 text-center text-xs">
|
||||||
|
Illustrative profile · Medscribe interface
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="lg:order-2">
|
||||||
|
<SectionLabel>Maintain the medication picture</SectionLabel>
|
||||||
|
<h2 className="text-foreground mt-4 font-display text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||||
|
Keep the daily routine easy to read.
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-5 leading-relaxed">
|
||||||
|
See medication details, schedules, reminders, and refill
|
||||||
|
information together without hunting through separate notes.
|
||||||
|
</p>
|
||||||
|
<p className="text-muted-foreground mt-5 text-sm leading-relaxed">
|
||||||
|
Keep medication timing easy to read and confirm changes with
|
||||||
|
the care professional who gave you the instructions.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-12 lg:grid-cols-2 lg:items-center">
|
||||||
|
<div className="lg:order-2">
|
||||||
|
<SectionLabel>Keep the next question</SectionLabel>
|
||||||
|
<h2 className="text-foreground mt-4 font-display text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||||
|
Save it while it is on your mind.
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-5 leading-relaxed">
|
||||||
|
Add questions as they come up between visits. If Ask reaches a
|
||||||
|
question that needs professional judgment, Medscribe can offer
|
||||||
|
to save it and suggest who may be helpful to ask.
|
||||||
|
</p>
|
||||||
|
<p className="text-muted-foreground mt-5 text-sm leading-relaxed">
|
||||||
|
Your list is ready when the appointment starts, so the most
|
||||||
|
important question does not disappear in the moment.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="justify-self-center py-2 lg:order-1">
|
||||||
|
<PhoneMockup
|
||||||
|
src="/screenshots/product-bezel-2026-08-24-unified-type/questions-black.webp"
|
||||||
|
darkSrc="/screenshots/product-bezel-2026-08-24-unified-type/questions-black-dark.webp"
|
||||||
|
alt="Illustrative Medscribe Questions to ask interface"
|
||||||
|
width={280}
|
||||||
|
/>
|
||||||
|
<p className="text-muted-foreground mt-4 text-center text-xs">
|
||||||
|
Illustrative profile · Medscribe interface
|
||||||
|
</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-display 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 Know Me when someone needs a concise picture. Review the
|
||||||
|
details first, then show or share the selected context with a
|
||||||
|
trusted caregiver.
|
||||||
|
</p>
|
||||||
|
<p className="text-muted-foreground mt-5 text-sm leading-relaxed">
|
||||||
|
You stay in control of the handoff: what is included, who sees
|
||||||
|
it, and when the conversation happens.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="justify-self-center py-2">
|
||||||
|
<PhoneMockup
|
||||||
|
src="/screenshots/product-bezel-2026-08-24-unified-type/know-me-natural.webp"
|
||||||
|
darkSrc="/screenshots/product-bezel-2026-08-24-unified-type/know-me-natural-dark.webp"
|
||||||
|
alt="Illustrative Medscribe Know Me handoff"
|
||||||
|
width={280}
|
||||||
|
/>
|
||||||
|
<p className="text-muted-foreground mt-4 text-center text-xs">
|
||||||
|
Illustrative profile · Medscribe interface
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="section-wash px-4 py-20 sm:px-6">
|
||||||
|
<div className="mx-auto grid max-w-6xl gap-12 lg:grid-cols-2">
|
||||||
|
<div>
|
||||||
|
<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-display 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="surface-panel text-foreground flex gap-3 rounded-2xl 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="night-card mx-auto flex max-w-6xl flex-col items-start justify-between gap-7 rounded-[2rem] px-7 py-10 text-white sm:px-10 md:flex-row md:items-center">
|
||||||
|
<div>
|
||||||
|
<h2 className="font-display text-3xl font-semibold">
|
||||||
|
Privacy should feel understandable.
|
||||||
|
</h2>
|
||||||
|
<p className="mt-2 text-sm leading-relaxed text-teal-50/75">
|
||||||
|
See how Medscribe keeps you in control of the context you keep,
|
||||||
|
review, and share.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
asChild
|
||||||
|
size="lg"
|
||||||
|
variant="outline"
|
||||||
|
className="border-white/20 bg-white text-[#0f4f4b] hover:bg-teal-50"
|
||||||
|
>
|
||||||
|
<Link href="/privacy">
|
||||||
|
Read about privacy
|
||||||
|
<ArrowRight className="size-4" aria-hidden="true" />
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Medscribe",
|
||||||
|
robots: { index: false, follow: false, nocache: true },
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function LandscapePage() {
|
||||||
|
redirect("/");
|
||||||
|
}
|
||||||
@@ -7,9 +7,15 @@ export default function MarketingLayout({
|
|||||||
}: Readonly<{ children: React.ReactNode }>) {
|
}: Readonly<{ children: React.ReactNode }>) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<a
|
||||||
|
href="#main-content"
|
||||||
|
className="print-hidden bg-primary-button fixed top-2 left-2 z-[100] -translate-y-20 rounded-lg px-4 py-2 text-sm font-semibold text-white transition-transform focus:translate-y-0"
|
||||||
|
>
|
||||||
|
Skip to content
|
||||||
|
</a>
|
||||||
<ScreenBackground />
|
<ScreenBackground />
|
||||||
<SiteHeader />
|
<SiteHeader />
|
||||||
<main>{children}</main>
|
<main id="main-content">{children}</main>
|
||||||
<SiteFooter />
|
<SiteFooter />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,345 +1,608 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
import {
|
import {
|
||||||
AlertTriangle,
|
ArrowRight,
|
||||||
ChevronRight,
|
Check,
|
||||||
ClipboardList,
|
ContactRound,
|
||||||
HeartPulse,
|
Download,
|
||||||
Lock,
|
FileText,
|
||||||
Mic,
|
HeartHandshake,
|
||||||
Pill,
|
LockKeyhole,
|
||||||
Shield,
|
ScanLine,
|
||||||
Users,
|
ShieldCheck,
|
||||||
|
Smartphone,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
import { FeatureShowcase } from "~/components/feature-showcase";
|
import { ProductDevice } from "~/components/product-device";
|
||||||
import { PhoneMockup } from "~/components/phone-mockup";
|
|
||||||
import { SectionLabel } from "~/components/section-label";
|
|
||||||
import { Badge } from "~/components/ui/badge";
|
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import {
|
import { productClaims, productRelease } from "~/content/product";
|
||||||
Card,
|
import { ACCESS_INVITE_MAILTO } from "~/lib/access";
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "~/components/ui/card";
|
|
||||||
|
|
||||||
const quickActions = [
|
export const metadata: Metadata = {
|
||||||
{
|
title: "Carry the context when care gets complicated",
|
||||||
icon: HeartPulse,
|
description:
|
||||||
title: "Know Me card",
|
"Medscribe is a private, patient-controlled companion that keeps visits, medications, and critical context together.",
|
||||||
meta: "Baseline, conditions, allergies",
|
};
|
||||||
featured: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: Pill,
|
|
||||||
title: "Time-critical meds",
|
|
||||||
meta: "Schedules caregivers can trust",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: Mic,
|
|
||||||
title: "Visit capture",
|
|
||||||
meta: "Consent-gated, on-device",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: Users,
|
|
||||||
title: "Caregiver handoff",
|
|
||||||
meta: "One current shared truth",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const pillars = [
|
const visitMoments = [
|
||||||
{
|
{
|
||||||
icon: ClipboardList,
|
number: "01",
|
||||||
title: "Keep the record current",
|
eyebrow: "Visits stay findable",
|
||||||
body: "Capture visits, scan labels, and keep conditions, medications, allergies, contacts, and caregiver notes in one portable record.",
|
title: "The visit has a place to land.",
|
||||||
|
body: "Keep the recording, transcript, and reviewed summary together instead of reconstructing the appointment from memory.",
|
||||||
|
image: "/screenshots/product-bezel-2026-08-24-unified-type/visits-natural.webp",
|
||||||
|
darkImage:
|
||||||
|
"/screenshots/product-bezel-2026-08-24-unified-type/visits-natural-dark.webp",
|
||||||
|
alt: "Medscribe Visits screen in an iPhone 17 Pro product bezel",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: AlertTriangle,
|
number: "02",
|
||||||
title: "Make the critical context obvious",
|
eyebrow: "Words stay checkable",
|
||||||
body: "The emergency view prioritizes the facts clinicians need fast: baseline, time-critical meds, active conditions, allergies, and who to call.",
|
title: "Go back to what was actually said.",
|
||||||
|
body: "Search the transcript and compare it with the generated summary before relying on it later.",
|
||||||
|
image:
|
||||||
|
"/screenshots/product-bezel-2026-08-24-unified-type/transcript-populated-black.webp",
|
||||||
|
darkImage:
|
||||||
|
"/screenshots/product-bezel-2026-08-24-unified-type/transcript-populated-black-dark.webp",
|
||||||
|
alt: "Medscribe visit transcript in an iPhone 17 Pro product bezel",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Shield,
|
number: "03",
|
||||||
title: "Stay private by architecture",
|
eyebrow: "The next step stays visible",
|
||||||
body: "Transcription, OCR, and medication chat run locally. No cloud AI processing. Sharing should be explicit, revocable, and encrypted.",
|
title: "Review the useful part. Keep your judgment.",
|
||||||
|
body: "Bring medications, follow-ups, diagnoses, and instructions into view, then check the details that matter.",
|
||||||
|
image:
|
||||||
|
"/screenshots/product-bezel-2026-08-24-unified-type/summary-reviewed-natural.webp",
|
||||||
|
darkImage:
|
||||||
|
"/screenshots/product-bezel-2026-08-24-unified-type/summary-reviewed-natural-dark.webp",
|
||||||
|
alt: "Medscribe appointment summary in an iPhone 17 Pro product bezel",
|
||||||
},
|
},
|
||||||
];
|
] as const;
|
||||||
|
|
||||||
const impact = [
|
const proofStats = [
|
||||||
{
|
{
|
||||||
title: "Fewer blank handoffs",
|
value: "One place",
|
||||||
body: "When a caregiver is overwhelmed, the important facts are already assembled: conditions, medications, allergies, baseline, contacts, and recent changes.",
|
label: "for visits, medications, and important context",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Safer medication moments",
|
value: "At a glance",
|
||||||
body: "Time-critical schedules and dose history stay visible, so a missed or delayed medication is easier to catch before it becomes a crisis.",
|
label: "for daily routines and reminders",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "More confident families",
|
value: "Your choice",
|
||||||
body: "Caregivers can see what changed, where it came from, and what still needs a clinician or pharmacist instead of guessing from memory.",
|
label: "when and what to share",
|
||||||
},
|
},
|
||||||
];
|
] as const;
|
||||||
|
|
||||||
|
const privacyPrinciples = [
|
||||||
|
{
|
||||||
|
icon: Smartphone,
|
||||||
|
title: "Built around the person",
|
||||||
|
body: productClaims.localRecord.publicCopy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: LockKeyhole,
|
||||||
|
title: "Private by design",
|
||||||
|
body: productClaims.localAi.publicCopy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: ShieldCheck,
|
||||||
|
title: "Care decisions stay human",
|
||||||
|
body: productClaims.medicationFacts.publicCopy,
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const accessFit = [
|
||||||
|
"Managing several medications or repeated visits",
|
||||||
|
"Coordinating care with a family caregiver",
|
||||||
|
"Keeping a clear record for the next conversation",
|
||||||
|
] as const;
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
const today = new Date().toLocaleDateString("en-US", {
|
|
||||||
weekday: "long",
|
|
||||||
month: "long",
|
|
||||||
day: "numeric",
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto max-w-6xl space-y-14 px-4 py-8 sm:px-6 sm:py-12 lg:space-y-20 lg:py-14">
|
<>
|
||||||
|
<section className="section-wash relative isolate min-h-[calc(100svh-4rem)] overflow-hidden px-4 pt-14 sm:px-6 sm:pt-20 lg:pt-24">
|
||||||
{/* Hero */}
|
<div className="mx-auto max-w-7xl text-center">
|
||||||
<section className="grid items-center gap-8 lg:grid-cols-[minmax(0,1fr)_minmax(230px,280px)] lg:gap-14">
|
<div className="animate-fade-in-up flex items-center justify-center gap-3">
|
||||||
<div className="space-y-6">
|
<span className="bg-success-light text-success rounded-full px-3 py-1.5 text-xs font-bold tracking-[0.12em] uppercase">
|
||||||
<div className="animate-fade-in-up space-y-5">
|
{productRelease.stage}
|
||||||
<SectionLabel>Medscribe · Private complex-care context</SectionLabel>
|
</span>
|
||||||
<h1 className="max-w-2xl font-serif text-4xl leading-[1.05] font-semibold tracking-tight text-foreground sm:text-5xl lg:text-6xl">
|
<span className="text-muted-foreground text-xs font-semibold tracking-[0.12em] uppercase">
|
||||||
Be known when care gets complicated
|
{productRelease.platforms}
|
||||||
</h1>
|
</span>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
|
<h1 className="animate-fade-in-up-delay-1 text-foreground mx-auto mt-7 max-w-6xl font-display text-[clamp(3.6rem,9vw,8.5rem)] leading-[0.86] font-semibold tracking-[-0.055em]">
|
||||||
<div className="animate-fade-in-up-delay-1 flex flex-wrap gap-3">
|
Carry the context.
|
||||||
|
</h1>
|
||||||
|
<p className="animate-fade-in-up-delay-2 text-muted-foreground mx-auto mt-7 max-w-2xl text-lg leading-relaxed sm:text-xl lg:text-2xl">
|
||||||
|
Visits, medications, and the critical details between them—kept
|
||||||
|
together wherever care takes you.
|
||||||
|
</p>
|
||||||
|
<div className="animate-fade-in-up-delay-3 mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
|
||||||
<Button asChild size="lg">
|
<Button asChild size="lg">
|
||||||
<a href="/executive-summary">
|
<Link href="/access">
|
||||||
Read executive summary
|
Request access
|
||||||
<ChevronRight className="size-4" />
|
<ArrowRight className="size-4" aria-hidden="true" />
|
||||||
</a>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button asChild variant="outline" size="lg">
|
<Button asChild size="lg" variant="outline">
|
||||||
<a href="#features">See how it works</a>
|
<Link href="/how-it-works">See how it works</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="animate-fade-in-up-delay-2 flex flex-wrap gap-2">
|
|
||||||
<Badge variant="success">On TestFlight</Badge>
|
|
||||||
<Badge variant="outline">iOS</Badge>
|
|
||||||
<Badge variant="outline">Emergency-ready</Badge>
|
|
||||||
<Badge variant="outline">No cloud AI</Badge>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="animate-fade-in-up-delay-3 flex justify-center lg:justify-end">
|
<div className="relative mx-auto mt-12 h-[44rem] max-w-5xl sm:h-[58rem] lg:mt-8 lg:h-[64rem]">
|
||||||
<div className="flex w-fit items-start justify-center">
|
<div className="absolute top-24 left-1/2 w-[17rem] -translate-x-[86%] -rotate-6 sm:top-28 sm:w-[22rem] lg:top-32 lg:w-[26rem]">
|
||||||
<PhoneMockup
|
<ProductDevice
|
||||||
src="/screenshots/home.png"
|
src="/screenshots/product-bezel-2026-08-24-unified-type/summary-reviewed-natural.webp"
|
||||||
alt="Medscribe home dashboard"
|
darkSrc="/screenshots/product-bezel-2026-08-24-unified-type/summary-reviewed-natural-dark.webp"
|
||||||
|
alt="Medscribe visit summary in an iPhone 17 Pro product bezel"
|
||||||
|
className="pitch-device-motion pitch-device-drift-left"
|
||||||
|
sizes="(min-width: 1024px) 416px, (min-width: 640px) 352px, 272px"
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
</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/product-bezel-2026-08-24-unified-type/home-black.webp"
|
||||||
|
darkSrc="/screenshots/product-bezel-2026-08-24-unified-type/home-black-dark.webp"
|
||||||
|
alt="Medscribe home screen in an iPhone 17 Pro product bezel"
|
||||||
|
className="pitch-device-motion pitch-device-drift-right"
|
||||||
|
sizes="(min-width: 1024px) 448px, (min-width: 640px) 368px, 288px"
|
||||||
priority
|
priority
|
||||||
width={190}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-muted-foreground relative z-20 mx-auto pb-8 text-center text-xs font-medium tracking-wide">
|
||||||
|
Illustrative profile · Medscribe interface
|
||||||
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Strategic spine */}
|
<section className="pitch-lazy section-wash flex min-h-[78svh] items-center px-4 py-24 sm:px-6 lg:py-32">
|
||||||
<section className="animate-fade-in-up space-y-5">
|
<div className="pitch-copy-motion mx-auto max-w-6xl text-center">
|
||||||
<SectionLabel>The story</SectionLabel>
|
<p className="text-primary text-xs font-bold tracking-[0.2em] uppercase">
|
||||||
<div className="grid gap-6 lg:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)] lg:items-start">
|
The connected picture
|
||||||
<div className="space-y-3">
|
</p>
|
||||||
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
<h2 className="text-foreground mx-auto mt-7 max-w-5xl font-display text-[clamp(2.8rem,7vw,6.5rem)] leading-[0.98] font-semibold tracking-[-0.045em]">
|
||||||
The story scatters right when it matters
|
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-display text-5xl leading-[0.98] font-semibold tracking-[-0.04em] sm:text-6xl">
|
||||||
|
Turn what happened into something you can check.
|
||||||
</h2>
|
</h2>
|
||||||
<p className="max-w-xl leading-relaxed text-muted-foreground">
|
<p className="text-muted-foreground mt-7 max-w-lg text-lg leading-relaxed">
|
||||||
A daughter remembers the medication schedule. A spouse knows what
|
Bring the important details from a visit into one reviewable
|
||||||
normal looks like. A specialist mentioned a change three visits ago.
|
record. You decide what is useful and what belongs in the picture
|
||||||
In a crisis, those details are scattered across people, portals, and
|
you carry forward.
|
||||||
memory.
|
</p>
|
||||||
|
<div className="surface-panel mt-8 max-w-lg rounded-2xl 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, index) => (
|
||||||
|
<li
|
||||||
|
key={moment.number}
|
||||||
|
className="pitch-card-motion surface-panel relative overflow-hidden rounded-[2.5rem] px-5 pt-8 sm:px-10 sm:pt-10"
|
||||||
|
>
|
||||||
|
<div className="relative z-10 max-w-xl">
|
||||||
|
<div className="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-display 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}
|
||||||
|
darkSrc={moment.darkImage}
|
||||||
|
alt={moment.alt}
|
||||||
|
className={`pitch-device-motion ${index % 2 === 0 ? "pitch-device-drift-right" : "pitch-device-drift-left"}`}
|
||||||
|
sizes="(min-width: 1024px) 336px, (min-width: 640px) 320px, 72vw"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="pitch-lazy brand-night relative isolate overflow-hidden px-4 py-24 text-white sm:px-6 lg:py-36">
|
||||||
|
<div className="mx-auto max-w-7xl">
|
||||||
|
<div className="pitch-copy-motion 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-display text-[clamp(3rem,7vw,6.7rem)] leading-[0.94] font-semibold tracking-[-0.045em]">
|
||||||
|
Keep the medication picture current.
|
||||||
|
</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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-lg border border-border-soft bg-surface p-5 shadow-[0_1px_4px_rgba(0,0,0,0.05)]">
|
|
||||||
<div className="flex items-start gap-3">
|
<div className="mt-16 grid gap-14 lg:grid-cols-2 lg:items-center lg:gap-6">
|
||||||
<HeartPulse className="mt-0.5 size-5 shrink-0 text-primary" />
|
<div className="relative mx-auto h-[42rem] w-full max-w-2xl sm:h-[50rem] lg:h-[56rem]">
|
||||||
<div className="space-y-2">
|
<div className="absolute top-20 left-[4%] w-[17rem] -rotate-6 sm:w-[21rem] lg:w-[23rem]">
|
||||||
<h3 className="font-serif text-xl font-semibold tracking-tight">
|
<ProductDevice
|
||||||
One current story
|
src="/screenshots/product-bezel-2026-08-24-unified-type/medication-detail-natural.webp"
|
||||||
|
darkSrc="/screenshots/product-bezel-2026-08-24-unified-type/medication-detail-natural-dark.webp"
|
||||||
|
alt="Medscribe medication detail in an iPhone 17 Pro product bezel"
|
||||||
|
className="pitch-device-motion pitch-device-drift-left"
|
||||||
|
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/product-bezel-2026-08-24-unified-type/medications-black.webp"
|
||||||
|
darkSrc="/screenshots/product-bezel-2026-08-24-unified-type/medications-black-dark.webp"
|
||||||
|
alt="Medscribe medication list in an iPhone 17 Pro product bezel"
|
||||||
|
className="pitch-device-motion pitch-device-drift-right"
|
||||||
|
sizes="(min-width: 1024px) 384px, (min-width: 640px) 352px, 288px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="overflow-hidden rounded-[2rem] bg-white/[0.055]">
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
title: "Keep schedules easy to understand",
|
||||||
|
body: "See timing, reminders, and refill details together in one calm view.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "See the important details",
|
||||||
|
body: productClaims.medicationFacts.publicCopy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Make review simple",
|
||||||
|
body: "Keep the details readable so you can check them with a clinician or pharmacist.",
|
||||||
|
},
|
||||||
|
].map((item) => (
|
||||||
|
<article
|
||||||
|
key={item.title}
|
||||||
|
className="border-white/10 border-t p-7 first:border-t-0 sm:p-9"
|
||||||
|
>
|
||||||
|
<Check className="size-5 text-teal-200" aria-hidden="true" />
|
||||||
|
<h3 className="mt-5 font-display 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 section-wash overflow-hidden px-4 py-24 sm:px-6 lg:py-36">
|
||||||
|
<div
|
||||||
|
id="questions"
|
||||||
|
className="mx-auto grid max-w-7xl scroll-mt-4 items-center gap-14 lg:grid-cols-[0.82fr_1.18fr] lg:gap-20"
|
||||||
|
>
|
||||||
|
<div className="pitch-copy-motion">
|
||||||
|
<p className="text-primary text-xs font-bold tracking-[0.2em] uppercase">
|
||||||
|
Questions for the doctor
|
||||||
|
</p>
|
||||||
|
<h2 className="text-foreground mt-6 max-w-2xl font-display text-[clamp(3.25rem,6vw,6.5rem)] leading-[0.94] font-semibold tracking-[-0.045em]">
|
||||||
|
Ask now. Save what belongs in the room.
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-8 max-w-xl text-lg leading-relaxed sm:text-xl">
|
||||||
|
Ask about a visit or medication. When the answer needs a care
|
||||||
|
professional's judgment, Medscribe can suggest saving the
|
||||||
|
question and who may be helpful to ask.
|
||||||
|
</p>
|
||||||
|
<div className="text-muted-foreground mt-7 flex max-w-xl items-start gap-3 text-sm leading-relaxed">
|
||||||
|
<span className="bg-primary-light text-primary mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full">
|
||||||
|
<Check className="size-3.5" aria-hidden="true" />
|
||||||
|
</span>
|
||||||
|
<p>
|
||||||
|
Keep a calm, ready list for your next appointment instead of
|
||||||
|
relying on memory when the conversation begins.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mx-auto grid w-full max-w-[40rem] items-center justify-center gap-2 sm:grid-cols-2 lg:justify-end">
|
||||||
|
<div className="mx-auto w-[min(72vw,18rem)] -rotate-2 sm:w-full">
|
||||||
|
<ProductDevice
|
||||||
|
src="/screenshots/product-bezel-2026-08-24-unified-type/ask-questions-black.webp"
|
||||||
|
darkSrc="/screenshots/product-bezel-2026-08-24-unified-type/ask-questions-black-dark.webp"
|
||||||
|
alt="Medscribe Ask response shown in an iPhone 17 Pro product bezel"
|
||||||
|
className="pitch-device-motion pitch-device-drift-left"
|
||||||
|
sizes="(min-width: 640px) 288px, 72vw"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mx-auto w-[min(72vw,18rem)] rotate-2 sm:w-full">
|
||||||
|
<ProductDevice
|
||||||
|
src="/screenshots/product-bezel-2026-08-24-unified-type/questions-black.webp"
|
||||||
|
darkSrc="/screenshots/product-bezel-2026-08-24-unified-type/questions-black-dark.webp"
|
||||||
|
alt="Medscribe Questions to ask interface shown in an iPhone 17 Pro product bezel"
|
||||||
|
className="pitch-device-motion pitch-device-drift-right"
|
||||||
|
sizes="(min-width: 640px) 288px, 72vw"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="pitch-lazy overflow-hidden px-4 py-24 sm:px-6 lg:py-36">
|
||||||
|
<div className="mx-auto max-w-7xl">
|
||||||
|
<div className="grid items-center gap-16 lg:grid-cols-[0.92fr_1.08fr] lg:gap-24">
|
||||||
|
<div className="relative mx-auto w-[min(80vw,28rem)]">
|
||||||
|
<div className="bg-primary-light/70 absolute inset-[12%_-12%_8%] -z-10 rounded-[4rem] blur-2xl" />
|
||||||
|
<ProductDevice
|
||||||
|
src="/screenshots/product-bezel-2026-08-24-unified-type/know-me-natural.webp"
|
||||||
|
darkSrc="/screenshots/product-bezel-2026-08-24-unified-type/know-me-natural-dark.webp"
|
||||||
|
alt="Medscribe Know Me handoff record in an iPhone 17 Pro product bezel"
|
||||||
|
className="pitch-device-motion pitch-device-drift-left"
|
||||||
|
sizes="(min-width: 1024px) 448px, (min-width: 640px) 400px, 80vw"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="pitch-copy-motion">
|
||||||
|
<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-display text-[clamp(3.2rem,6vw,6.2rem)] leading-[0.95] font-semibold tracking-[-0.045em]">
|
||||||
|
Bring the picture when care gets complicated.
|
||||||
|
</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 that is easy
|
||||||
|
to review together.
|
||||||
|
</p>
|
||||||
|
<div className="mt-9 grid gap-4 sm:grid-cols-2">
|
||||||
|
<div className="surface-panel rounded-2xl 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="surface-panel rounded-2xl 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">
|
||||||
|
Share selected context with someone you trust.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="night-card mt-24 overflow-hidden rounded-[2.5rem] p-7 text-white sm:p-10 lg:p-14">
|
||||||
|
<div className="grid gap-12 lg:grid-cols-[0.85fr_1.15fr] lg:items-center">
|
||||||
|
<div>
|
||||||
|
<HeartHandshake
|
||||||
|
className="size-7 text-teal-200"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<h3 className="mt-5 font-display text-4xl leading-tight font-semibold sm:text-5xl">
|
||||||
|
Share with intention.
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm leading-relaxed text-muted-foreground">
|
<p className="mt-5 max-w-xl leading-relaxed text-teal-50/75">
|
||||||
Medscribe gives the family a private place to keep that context
|
Choose the context that helps, review it first, and share it
|
||||||
alive: current meds, timing, baseline, allergies, recent visit
|
with a trusted caregiver when the moment calls for it.
|
||||||
summaries, emergency contacts, and the source trail behind each
|
|
||||||
fact.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<ol className="grid gap-3 sm:grid-cols-3">
|
||||||
|
{[
|
||||||
|
["1", "You review", "Choose the context that helps"],
|
||||||
|
["2", "You share", "Send it to someone you trust"],
|
||||||
|
["3", "Stay aligned", "Keep the same picture in view"],
|
||||||
|
].map(([number, title, body]) => (
|
||||||
|
<li
|
||||||
|
key={number}
|
||||||
|
className="rounded-2xl bg-white/[0.065] p-5"
|
||||||
|
>
|
||||||
|
<span className="flex size-8 items-center justify-center rounded-full bg-white/12 text-xs font-bold">
|
||||||
|
{number}
|
||||||
|
</span>
|
||||||
|
<p className="mt-8 font-display text-xl font-semibold">
|
||||||
|
{title}
|
||||||
|
</p>
|
||||||
|
<p className="mt-2 text-sm leading-relaxed text-teal-50/65">
|
||||||
|
{body}
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
</div>
|
</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"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<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>
|
</section>
|
||||||
|
|
||||||
{/* Interactive feature showcase */}
|
<section className="pitch-lazy brand-night relative isolate overflow-hidden px-4 py-24 text-white sm:px-6 lg:py-36">
|
||||||
<section id="features" className="animate-fade-in-up scroll-mt-24 space-y-5">
|
<div className="absolute inset-0 -z-10 bg-[radial-gradient(circle_at_50%_0%,rgba(45,212,191,0.13),transparent_36%)]" />
|
||||||
<div className="space-y-2">
|
<div className="mx-auto max-w-7xl">
|
||||||
<SectionLabel>The record</SectionLabel>
|
<div className="pitch-copy-motion mx-auto max-w-4xl text-center">
|
||||||
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
<p className="text-xs font-bold tracking-[0.2em] text-teal-300 uppercase">
|
||||||
The pieces that keep the story current
|
Privacy that feels personal
|
||||||
</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.
|
|
||||||
</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>
|
</p>
|
||||||
</CardContent>
|
<h2 className="mt-6 font-display text-[clamp(3.2rem,7vw,7rem)] leading-[0.92] font-semibold tracking-[-0.05em]">
|
||||||
</Card>
|
Your health story stays centered on you.
|
||||||
</section>
|
</h2>
|
||||||
|
<p className="mx-auto mt-8 max-w-2xl text-lg leading-relaxed text-slate-300 sm:text-xl">
|
||||||
|
Medscribe keeps the experience focused on your choices: what you
|
||||||
|
keep, what you review, and what you share with people you trust.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Screenshot strip */}
|
<dl className="mt-20 grid gap-10 md:grid-cols-3 md:gap-0 md:divide-x md:divide-white/12">
|
||||||
<section className="animate-fade-in-up space-y-6">
|
{proofStats.map((stat) => (
|
||||||
<SectionLabel>What travels with them</SectionLabel>
|
<div
|
||||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
key={stat.label}
|
||||||
{[
|
className="px-5 py-2 text-center md:py-10 lg:py-14"
|
||||||
{ src: "/screenshots/emergency-record.png", label: "Emergency record" },
|
>
|
||||||
{ src: "/screenshots/appointment-summary.png", label: "Visit summary" },
|
<dt className="text-sm text-slate-400">{stat.label}</dt>
|
||||||
{ src: "/screenshots/medications.png", label: "Medication list" },
|
<dd className="mt-3 font-display text-5xl font-semibold tracking-[-0.04em] text-teal-200 sm:text-6xl lg:text-7xl">
|
||||||
{ src: "/screenshots/chat.png", label: "Medication questions" },
|
{stat.value}
|
||||||
].map((shot) => (
|
</dd>
|
||||||
<Card key={shot.src} className="card-hover overflow-hidden">
|
|
||||||
<div className="flex min-h-[270px] items-start justify-center bg-surface-soft p-4">
|
|
||||||
<PhoneMockup src={shot.src} alt={shot.label} width={112} />
|
|
||||||
</div>
|
</div>
|
||||||
<p className="border-t border-border-soft px-4 py-3 text-center text-sm font-medium text-muted-foreground">
|
))}
|
||||||
{shot.label}
|
</dl>
|
||||||
</p>
|
|
||||||
</Card>
|
<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] bg-white/[0.05] p-7"
|
||||||
|
>
|
||||||
|
<Icon className="size-6 text-teal-200" aria-hidden="true" />
|
||||||
|
<h3 className="mt-8 font-display 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 about privacy
|
||||||
|
<ArrowRight className="size-4" aria-hidden="true" />
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Impact */}
|
<section
|
||||||
<section id="impact" className="animate-fade-in-up scroll-mt-24 space-y-6">
|
id="partners"
|
||||||
<div className="space-y-3">
|
className="pitch-lazy overflow-hidden px-4 py-20 sm:px-6 lg:py-28"
|
||||||
<SectionLabel>Potential impact</SectionLabel>
|
>
|
||||||
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
<div className="section-wash surface-panel mx-auto grid max-w-6xl gap-10 rounded-[2.75rem] px-6 py-12 sm:px-10 lg:grid-cols-[1fr_auto] lg:items-center lg:px-14 lg:py-16">
|
||||||
Less panic, more context, better handoffs
|
<div className="pitch-copy-motion max-w-3xl">
|
||||||
</h2>
|
<div className="bg-primary-light text-primary flex size-12 items-center justify-center rounded-2xl">
|
||||||
<p className="max-w-2xl text-muted-foreground">
|
<FileText className="size-6" aria-hidden="true" />
|
||||||
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">Try Medscribe on iOS</CardTitle>
|
|
||||||
<CardDescription className="text-white/75">
|
|
||||||
The app is live on TestFlight. Reach out for an invite if
|
|
||||||
you're managing medications, visits, or emergency context for
|
|
||||||
someone you love.
|
|
||||||
</CardDescription>
|
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<p className="text-primary mt-7 text-xs font-bold tracking-[0.2em] uppercase">
|
||||||
asChild
|
For partners
|
||||||
size="lg"
|
</p>
|
||||||
variant="outline"
|
<h2 className="text-foreground mt-4 font-display text-4xl leading-tight font-semibold tracking-[-0.035em] sm:text-5xl">
|
||||||
className="border-white/30 bg-white text-primary-button hover:bg-white/90"
|
See the partnership story.
|
||||||
>
|
</h2>
|
||||||
<a href="mailto:sean@soconnor.dev?subject=Medscribe%20TestFlight%20invite%20request&body=Hi%20Sean%2C%0D%0A%0D%0AI%27d%20like%20a%20TestFlight%20invite%20to%20try%20Medscribe.%20I%27m%20managing%20medications%2C%20visits%2C%20or%20emergency%20context%20for%20someone%20I%20care%20for.%0D%0A%0D%0AThanks%21">
|
<p className="text-muted-foreground mt-5 max-w-2xl text-lg leading-relaxed">
|
||||||
Request an invite
|
An eight-slide overview of the problem Medscribe addresses, the
|
||||||
|
product in practice, and the opportunity to bring it to more
|
||||||
|
patients and caregivers.
|
||||||
|
</p>
|
||||||
|
<p className="text-foreground mt-6 text-sm font-semibold tracking-[-0.01em]">
|
||||||
|
<span className="block">Hadlock Holdings LLC</span>
|
||||||
|
<span className="text-muted-foreground mt-1 block">
|
||||||
|
Sean O'Connor · Bill Vitriol
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3 sm:flex-row lg:flex-col">
|
||||||
|
<Button asChild size="lg">
|
||||||
|
<a
|
||||||
|
href="/medscribe-corporate-partner-overview.pdf"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
View pitch deck
|
||||||
|
<ArrowRight className="size-4" aria-hidden="true" />
|
||||||
</a>
|
</a>
|
||||||
</Button>
|
</Button>
|
||||||
</CardHeader>
|
<Button asChild size="lg" variant="outline">
|
||||||
</Card>
|
<a href="/medscribe-corporate-partner-overview.pdf" download>
|
||||||
|
Download PDF
|
||||||
|
<Download className="size-4" aria-hidden="true" />
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
<section className="pitch-lazy flex min-h-[82svh] items-center px-4 py-24 sm:px-6 lg:py-32">
|
||||||
|
<div className="pitch-copy-motion section-wash surface-panel mx-auto w-full max-w-6xl overflow-hidden rounded-[3rem] px-6 py-16 text-center sm:px-10 lg:px-20 lg:py-24">
|
||||||
|
<p className="text-primary text-xs font-bold tracking-[0.2em] uppercase">
|
||||||
|
Made for people who carry a lot
|
||||||
|
</p>
|
||||||
|
<h2 className="text-foreground mx-auto mt-6 max-w-4xl font-display text-[clamp(3.2rem,7vw,7rem)] leading-[0.92] font-semibold tracking-[-0.05em]">
|
||||||
|
Carry the record that helps you move forward.
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground mx-auto mt-8 max-w-2xl text-lg leading-relaxed sm:text-xl">
|
||||||
|
Bring visits, medication routines, and trusted caregivers into one
|
||||||
|
clearer picture—without losing your place at the center.
|
||||||
|
</p>
|
||||||
|
<ul className="mx-auto mt-9 grid max-w-4xl gap-3 text-left md:grid-cols-3">
|
||||||
|
{accessFit.map((item) => (
|
||||||
|
<li
|
||||||
|
key={item}
|
||||||
|
className="bg-surface/65 text-foreground flex items-start gap-3 rounded-2xl p-4 text-sm leading-relaxed"
|
||||||
|
>
|
||||||
|
<span className="bg-primary-light mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full">
|
||||||
|
<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={ACCESS_INVITE_MAILTO}>
|
||||||
|
Request access
|
||||||
|
<ArrowRight className="size-4" aria-hidden="true" />
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
<Button asChild size="lg" variant="outline">
|
||||||
|
<Link href="/access">Explore Medscribe</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 interested as a
|
||||||
|
patient, caregiver, or both. Please do not email health details.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,191 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import {
|
||||||
|
ArrowRight,
|
||||||
|
Check,
|
||||||
|
EyeOff,
|
||||||
|
Hand,
|
||||||
|
Share2,
|
||||||
|
ShieldCheck,
|
||||||
|
UserRoundCheck,
|
||||||
|
} from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import { SectionLabel } from "~/components/section-label";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Privacy",
|
||||||
|
description:
|
||||||
|
"A plain-language view of how Medscribe keeps people in control of their care context.",
|
||||||
|
};
|
||||||
|
|
||||||
|
const principles = [
|
||||||
|
{
|
||||||
|
icon: Hand,
|
||||||
|
title: "You decide what belongs",
|
||||||
|
body: "Add, review, update, or remove the details that help you manage visits and medications.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Share2,
|
||||||
|
title: "You choose when to share",
|
||||||
|
body: "Show or share selected context with a trusted caregiver or care professional when it helps.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: UserRoundCheck,
|
||||||
|
title: "You remain the center",
|
||||||
|
body: "Medscribe supports your conversations without taking decisions away from you or your care team.",
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const commitments = [
|
||||||
|
{
|
||||||
|
title: "No advertising profile",
|
||||||
|
body: "Your health context is not for sale and is not used to target advertising.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Deliberate sharing",
|
||||||
|
body: "Caregiver access begins with your choice, and shared information stays focused on the context you select.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Clear review",
|
||||||
|
body: "Summaries and organized details remain easy to read and check before you rely on or share them.",
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const choices = [
|
||||||
|
"Keep visit notes, summaries, and follow-ups together",
|
||||||
|
"Maintain a medication list and reminder routine",
|
||||||
|
"Prepare a concise Know Me view for important moments",
|
||||||
|
"Share selected context with someone you trust",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export default function PrivacyPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section className="px-4 pt-16 pb-20 sm:px-6 sm:pt-22 lg:pb-28">
|
||||||
|
<div className="mx-auto max-w-5xl text-center">
|
||||||
|
<div className="bg-primary-light text-primary mx-auto flex size-14 items-center justify-center rounded-2xl">
|
||||||
|
<ShieldCheck className="size-7" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
<SectionLabel className="mt-6">Privacy</SectionLabel>
|
||||||
|
<h1 className="text-foreground mx-auto mt-5 max-w-4xl font-display text-5xl leading-[1.02] font-semibold tracking-[-0.035em] sm:text-6xl lg:text-7xl">
|
||||||
|
Your story stays yours.
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground mx-auto mt-7 max-w-2xl text-lg leading-relaxed sm:text-xl">
|
||||||
|
Medscribe is designed around a simple idea: you should be able to
|
||||||
|
carry useful care context without giving up control of it.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="section-wash px-4 py-20 sm:px-6 lg:py-24">
|
||||||
|
<div className="mx-auto grid max-w-6xl gap-4 md:grid-cols-3">
|
||||||
|
{principles.map((item) => {
|
||||||
|
const Icon = item.icon;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
key={item.title}
|
||||||
|
className="surface-panel rounded-2xl p-6"
|
||||||
|
>
|
||||||
|
<Icon className="text-primary size-6" aria-hidden="true" />
|
||||||
|
<h2 className="text-foreground mt-5 font-display text-2xl font-semibold">
|
||||||
|
{item.title}
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-3 text-sm leading-relaxed">
|
||||||
|
{item.body}
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="px-4 py-20 sm:px-6 lg:py-28">
|
||||||
|
<div className="mx-auto grid max-w-6xl gap-14 lg:grid-cols-[0.78fr_1.22fr] lg:items-start">
|
||||||
|
<div>
|
||||||
|
<SectionLabel>Plain-language commitments</SectionLabel>
|
||||||
|
<h2 className="text-foreground mt-4 font-display text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||||
|
Privacy should feel understandable.
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-5 leading-relaxed">
|
||||||
|
The important part is not technical vocabulary. It is knowing what
|
||||||
|
Medscribe is for, what you can control, and when another person
|
||||||
|
sees your information.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4">
|
||||||
|
{commitments.map((item) => (
|
||||||
|
<article
|
||||||
|
key={item.title}
|
||||||
|
className="surface-panel rounded-2xl p-6 sm:p-7"
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="bg-primary-light text-primary flex size-10 shrink-0 items-center justify-center rounded-xl">
|
||||||
|
<EyeOff className="size-5" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-foreground font-display text-2xl font-semibold">
|
||||||
|
{item.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-muted-foreground mt-2 text-sm leading-relaxed">
|
||||||
|
{item.body}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="brand-night px-4 py-20 text-white sm:px-6 lg:py-24">
|
||||||
|
<div className="mx-auto grid max-w-6xl gap-12 lg:grid-cols-2 lg:items-center">
|
||||||
|
<div>
|
||||||
|
<p className="text-xs font-bold tracking-[0.16em] text-teal-200 uppercase">
|
||||||
|
Your choices
|
||||||
|
</p>
|
||||||
|
<h2 className="mt-5 font-display text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||||
|
Keep the context that helps. Share it when it matters.
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<ul className="grid gap-3">
|
||||||
|
{choices.map((item) => (
|
||||||
|
<li
|
||||||
|
key={item}
|
||||||
|
className="flex items-start gap-3 rounded-xl bg-white/[0.06] p-4 text-sm leading-relaxed text-teal-50/90"
|
||||||
|
>
|
||||||
|
<Check
|
||||||
|
className="mt-0.5 size-4 shrink-0 text-teal-200"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
{item}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="px-4 py-18 sm:px-6">
|
||||||
|
<div className="surface-panel mx-auto flex max-w-6xl flex-col items-start justify-between gap-6 rounded-[2rem] p-7 sm:flex-row sm:items-center sm:p-9">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-foreground font-display text-3xl font-semibold">
|
||||||
|
See how Medscribe fits into the day.
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground mt-2 text-sm leading-relaxed">
|
||||||
|
Follow the journey from a visit to a medication routine and a
|
||||||
|
trusted handoff.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button asChild size="lg">
|
||||||
|
<Link href="/how-it-works">
|
||||||
|
See how it works
|
||||||
|
<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-display 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -8,7 +8,10 @@ import {
|
|||||||
const COOKIE_MAX_AGE = 60 * 60 * 24 * 30;
|
const COOKIE_MAX_AGE = 60 * 60 * 24 * 30;
|
||||||
|
|
||||||
export async function POST(req: Request) {
|
export async function POST(req: Request) {
|
||||||
const password = process.env.SITE_PASSWORD;
|
const password =
|
||||||
|
process.env.SITE_PASSWORD_ENABLED === "true"
|
||||||
|
? process.env.SITE_PASSWORD
|
||||||
|
: undefined;
|
||||||
|
|
||||||
if (!password) {
|
if (!password) {
|
||||||
return NextResponse.json({ ok: true });
|
return NextResponse.json({ ok: true });
|
||||||
|
|||||||
@@ -1,25 +1,23 @@
|
|||||||
import "~/styles/globals.css";
|
import "~/styles/globals.css";
|
||||||
|
|
||||||
import { type Metadata } from "next";
|
import { type Metadata, type Viewport } from "next";
|
||||||
import { Instrument_Sans, Playfair_Display } from "next/font/google";
|
import { Instrument_Sans } from "next/font/google";
|
||||||
|
|
||||||
|
import { UmamiAnalytics } from "~/components/umami-analytics";
|
||||||
|
|
||||||
const instrumentSans = Instrument_Sans({
|
const instrumentSans = Instrument_Sans({
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
variable: "--font-instrument-sans",
|
variable: "--font-instrument-sans",
|
||||||
});
|
});
|
||||||
|
|
||||||
const playfair = Playfair_Display({
|
|
||||||
subsets: ["latin"],
|
|
||||||
variable: "--font-playfair",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: {
|
title: {
|
||||||
default: "Medscribe | Be known when care gets complicated",
|
default: "Medscribe | Carry the context when care gets complicated",
|
||||||
template: "Medscribe | %s",
|
template: "Medscribe | %s",
|
||||||
},
|
},
|
||||||
description:
|
description:
|
||||||
"A private, emergency-ready medical context app for families managing complex care. Keep visits, medications, baseline, allergies, and caregiver handoffs current on device.",
|
"A private, patient-controlled companion for keeping visits, medications, and critical context together.",
|
||||||
|
applicationName: "Medscribe",
|
||||||
icons: {
|
icons: {
|
||||||
icon: [
|
icon: [
|
||||||
{ url: "/favicon.ico", sizes: "any" },
|
{ url: "/favicon.ico", sizes: "any" },
|
||||||
@@ -27,22 +25,34 @@ export const metadata: Metadata = {
|
|||||||
{ url: "/favicon-32x32.png", sizes: "32x32", type: "image/png" },
|
{ url: "/favicon-32x32.png", sizes: "32x32", type: "image/png" },
|
||||||
{ url: "/favicon-16x16.png", sizes: "16x16", type: "image/png" },
|
{ url: "/favicon-16x16.png", sizes: "16x16", type: "image/png" },
|
||||||
],
|
],
|
||||||
apple: [{ url: "/apple-touch-icon.png", sizes: "180x180", type: "image/png" }],
|
apple: [
|
||||||
|
{ url: "/apple-touch-icon.png", sizes: "180x180", type: "image/png" },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: "Medscribe",
|
title: "Medscribe",
|
||||||
description:
|
description:
|
||||||
"Be known when care gets complicated. Private medical context for families managing complex care.",
|
"Carry the context when care gets complicated. Private, patient-controlled health context when you need it.",
|
||||||
type: "website",
|
type: "website",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const viewport: Viewport = {
|
||||||
|
themeColor: [
|
||||||
|
{ media: "(prefers-color-scheme: light)", color: "#f4f9f7" },
|
||||||
|
{ media: "(prefers-color-scheme: dark)", color: "#071415" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{ children: React.ReactNode }>) {
|
}: Readonly<{ children: React.ReactNode }>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en" className={`${instrumentSans.variable} ${playfair.variable}`}>
|
<html lang="en" className={instrumentSans.variable}>
|
||||||
<body className="min-h-screen">{children}</body>
|
<body className="min-h-screen">
|
||||||
|
{children}
|
||||||
|
<UmamiAnalytics />
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,309 @@
|
|||||||
|
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-display 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, then review the useful details together.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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: "Bring medication questions into the same view as the patient's list, with important decisions left to the pharmacist or care team.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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 with intention",
|
||||||
|
body: "The patient chooses what to share with a trusted caregiver and when that handoff happens.",
|
||||||
|
},
|
||||||
|
] 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, centered on their choices rather than a sales pitch.",
|
||||||
|
cvsTitle: "Engagement without the liability",
|
||||||
|
cvs: "A CVS-branded habit patients can understand, with a clear privacy posture and deliberate patient control.",
|
||||||
|
},
|
||||||
|
] 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-display 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">
|
||||||
|
Medscribe organizes visits, reads pill bottles, supports
|
||||||
|
medication questions, and keeps one ready-to-share record across
|
||||||
|
every provider.
|
||||||
|
</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">
|
||||||
|
{[
|
||||||
|
"Private by design",
|
||||||
|
"Clear by design",
|
||||||
|
"Patient-controlled",
|
||||||
|
"iOS and Android",
|
||||||
|
].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-[202px]">
|
||||||
|
<div>
|
||||||
|
<Image
|
||||||
|
src="/screenshots/product-bezel-2026-08-24-unified-type/medications-black.webp"
|
||||||
|
alt="Medscribe medications screen"
|
||||||
|
width={1350}
|
||||||
|
height={2760}
|
||||||
|
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-display text-[25px] leading-[1.12] font-semibold text-slate-950">
|
||||||
|
The daily tools patients need, connected in one place
|
||||||
|
</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-display 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">
|
||||||
|
{[
|
||||||
|
[
|
||||||
|
"Private by design",
|
||||||
|
"The experience keeps the patient's choices at the center of every review and handoff.",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Clear medication context",
|
||||||
|
"Medication questions stay connected to the list the patient already carries and the professionals they trust.",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"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-display 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-display 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-display 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-display 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">
|
||||||
|
Designed for iOS and Android. A partnership can connect the
|
||||||
|
patient experience with CVS's adherence and pharmacy goals.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="northwell-print-label">Who is behind it</p>
|
||||||
|
<h3 className="mt-3 font-display 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,133 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import type { KeyboardEvent } from "react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import {
|
import {
|
||||||
CalendarDays,
|
CalendarDays,
|
||||||
Camera,
|
|
||||||
Check,
|
Check,
|
||||||
ContactRound,
|
ContactRound,
|
||||||
Home,
|
|
||||||
MessageCircle,
|
MessageCircle,
|
||||||
Shield,
|
Pill,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
import { PhoneMockup } from "~/components/phone-mockup";
|
import { PhoneMockup } from "~/components/phone-mockup";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
const features = [
|
const features = [
|
||||||
{
|
|
||||||
id: "home",
|
|
||||||
label: "Profile",
|
|
||||||
icon: Home,
|
|
||||||
title: "One profile for the person you care for",
|
|
||||||
description:
|
|
||||||
"Visits, medications, reminders, and questions live together under the person they belong to.",
|
|
||||||
points: [
|
|
||||||
"Switch between everyone you care for",
|
|
||||||
"One home for the whole picture",
|
|
||||||
"Nothing scattered across apps",
|
|
||||||
],
|
|
||||||
image: "/screenshots/home.png",
|
|
||||||
alt: "Medscribe home dashboard with record visit, search, medications, and ask shortcuts",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "context",
|
|
||||||
label: "Context",
|
|
||||||
icon: ContactRound,
|
|
||||||
title: "The context behind the handoff",
|
|
||||||
description:
|
|
||||||
"Conditions, medication changes, visit summaries, and caregiver notes stay close enough to become useful when time is short.",
|
|
||||||
points: [
|
|
||||||
"Baseline, allergies, and conditions up front",
|
|
||||||
"Time-critical medications flagged",
|
|
||||||
"A source trail behind each fact",
|
|
||||||
],
|
|
||||||
image: "/screenshots/emergency-record.png",
|
|
||||||
alt: "Medscribe emergency record with critical care context",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "visits",
|
id: "visits",
|
||||||
label: "Visits",
|
label: "Visits",
|
||||||
icon: CalendarDays,
|
icon: CalendarDays,
|
||||||
title: "Visit recordings that refresh the record",
|
title: "Turn a visit into context you can review",
|
||||||
description:
|
description:
|
||||||
"A visit can become searchable context, so a medication change or follow-up instruction does not depend on memory alone.",
|
"Keep the important details from a visit together in a summary you can check and revisit.",
|
||||||
points: [
|
points: [
|
||||||
"Consent-gated, on-device recording",
|
"Keep the visit and summary easy to revisit",
|
||||||
"Searchable transcript and summary",
|
"Review important details before relying on them",
|
||||||
"Follow-ups captured, not forgotten",
|
"Carry follow-ups into the next conversation",
|
||||||
],
|
],
|
||||||
image: "/screenshots/visit-timeline.png",
|
image: "/screenshots/product-bezel-2026-08-24-unified-type/visits-natural.webp",
|
||||||
alt: "Medscribe visit timeline with appointment cards",
|
darkImage:
|
||||||
|
"/screenshots/product-bezel-2026-08-24-unified-type/visits-natural-dark.webp",
|
||||||
|
alt: "Illustrative Medscribe visit detail showing a visit summary and follow-up information",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "meds",
|
id: "medications",
|
||||||
label: "Meds",
|
label: "Medications",
|
||||||
icon: Camera,
|
icon: Pill,
|
||||||
title: "Medication OCR, reminders, and history",
|
title: "Keep the daily medication picture close",
|
||||||
description:
|
description:
|
||||||
"Prescription labels, reminders, refill alerts, and dose history help the family see what is supposed to happen and what actually happened.",
|
"Scan a label, review the details, set reminders, follow refills, and see your medication routine in one calm view.",
|
||||||
points: [
|
points: [
|
||||||
"Scan labels with the camera",
|
"Review medication details before saving them",
|
||||||
"Reminders and refill alerts",
|
"Keep schedules and reminders easy to understand",
|
||||||
"Dose history at a glance",
|
"Bring important questions to a clinician or pharmacist",
|
||||||
],
|
],
|
||||||
image: "/screenshots/medications.png",
|
image:
|
||||||
alt: "Medscribe medications screen with active medications",
|
"/screenshots/product-bezel-2026-08-24-unified-type/medication-detail-natural.webp",
|
||||||
|
darkImage:
|
||||||
|
"/screenshots/product-bezel-2026-08-24-unified-type/medication-detail-natural-dark.webp",
|
||||||
|
alt: "Illustrative Medscribe medication detail with schedule and medication information",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "ask",
|
id: "questions",
|
||||||
label: "Ask",
|
label: "Questions",
|
||||||
icon: MessageCircle,
|
icon: MessageCircle,
|
||||||
title: "Medication questions grounded in the record",
|
title: "Keep the question for the right conversation",
|
||||||
description:
|
description:
|
||||||
"Plain-language questions can draw from the person's record and bundled FDA facts, while keeping diagnosis and dosing with clinicians.",
|
"Ask about a visit or medication. When professional judgment is needed, save the question and keep guidance on who may be helpful to ask.",
|
||||||
points: [
|
points: [
|
||||||
"Plain-language answers",
|
"Capture questions as they come up",
|
||||||
"Grounded in the record and FDA facts",
|
"Keep a ready list for the next appointment",
|
||||||
"Defers dosing and diagnosis to clinicians",
|
"See whether a doctor, prescriber, or pharmacist may be helpful",
|
||||||
],
|
],
|
||||||
image: "/screenshots/chat.png",
|
image:
|
||||||
alt: "Medscribe Ask chat answering a question about blood pressure medications",
|
"/screenshots/product-bezel-2026-08-24-unified-type/ask-questions-black.webp",
|
||||||
|
darkImage:
|
||||||
|
"/screenshots/product-bezel-2026-08-24-unified-type/ask-questions-black-dark.webp",
|
||||||
|
alt: "Illustrative Medscribe answer with medication context and a safety boundary",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "privacy",
|
id: "handoff",
|
||||||
label: "Privacy",
|
label: "Know Me",
|
||||||
icon: Shield,
|
icon: ContactRound,
|
||||||
title: "Private by architecture, not policy",
|
title: "Prepare one readable handoff",
|
||||||
description:
|
description:
|
||||||
"The sensitive work happens on device, so the family record is not another cloud inbox waiting to be mined.",
|
"The Know Me view brings selected medications, allergies, conditions, baseline, and contacts together. Current, unconfirmed, and review-needed states make uncertainty visible.",
|
||||||
points: [
|
points: [
|
||||||
"On-device AI, no cloud processing",
|
"Medication schedules stay easy to review",
|
||||||
"No inbox to breach or mine",
|
"The person can show or share the snapshot they reviewed",
|
||||||
"You control what is ever shared",
|
"It complements—not replaces—clinical verification",
|
||||||
],
|
],
|
||||||
image: "/screenshots/settings.png",
|
image:
|
||||||
alt: "Medscribe settings showing on-device AI models and privacy notice",
|
"/screenshots/product-bezel-2026-08-24-unified-type/know-me-natural.webp",
|
||||||
|
darkImage:
|
||||||
|
"/screenshots/product-bezel-2026-08-24-unified-type/know-me-natural-dark.webp",
|
||||||
|
alt: "Illustrative Medscribe Know Me handoff with active medications and review state",
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
type FeatureId = (typeof features)[number]["id"];
|
||||||
|
|
||||||
export function FeatureShowcase() {
|
export function FeatureShowcase() {
|
||||||
const [activeId, setActiveId] = useState<(typeof features)[number]["id"]>("home");
|
const [activeId, setActiveId] = useState<FeatureId>("visits");
|
||||||
const active = features.find((f) => f.id === activeId) ?? features[0];
|
const active =
|
||||||
|
features.find((feature) => feature.id === activeId) ?? features[0];
|
||||||
|
|
||||||
|
const onTabKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {
|
||||||
|
const activeIndex = features.findIndex(
|
||||||
|
(feature) => feature.id === activeId,
|
||||||
|
);
|
||||||
|
let nextIndex: number | undefined;
|
||||||
|
|
||||||
|
if (event.key === "ArrowRight" || event.key === "ArrowDown") {
|
||||||
|
nextIndex = (activeIndex + 1) % features.length;
|
||||||
|
} else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
|
||||||
|
nextIndex = (activeIndex - 1 + features.length) % features.length;
|
||||||
|
} else if (event.key === "Home") {
|
||||||
|
nextIndex = 0;
|
||||||
|
} else if (event.key === "End") {
|
||||||
|
nextIndex = features.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextIndex === undefined) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
const nextFeature = features[nextIndex];
|
||||||
|
if (!nextFeature) return;
|
||||||
|
|
||||||
|
setActiveId(nextFeature.id);
|
||||||
|
document.getElementById(`feature-tab-${nextFeature.id}`)?.focus();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid items-center gap-8 lg:grid-cols-[minmax(0,1fr)_300px] lg:gap-12">
|
<div className="grid items-center gap-10 lg:grid-cols-[minmax(0,1fr)_340px] lg:gap-16">
|
||||||
<div className="flex flex-col gap-5">
|
<div className="flex min-w-0 flex-col gap-6">
|
||||||
<div className="flex flex-wrap gap-2">
|
<div
|
||||||
|
role="tablist"
|
||||||
|
aria-label="Explore Medscribe"
|
||||||
|
className="flex max-w-full gap-2 overflow-x-auto pb-1"
|
||||||
|
>
|
||||||
{features.map((feature) => {
|
{features.map((feature) => {
|
||||||
const Icon = feature.icon;
|
const Icon = feature.icon;
|
||||||
const isActive = feature.id === activeId;
|
const isActive = feature.id === activeId;
|
||||||
@@ -122,49 +135,72 @@ export function FeatureShowcase() {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={feature.id}
|
key={feature.id}
|
||||||
|
id={`feature-tab-${feature.id}`}
|
||||||
type="button"
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-selected={isActive}
|
||||||
|
aria-controls={`feature-panel-${feature.id}`}
|
||||||
|
tabIndex={isActive ? 0 : -1}
|
||||||
onClick={() => setActiveId(feature.id)}
|
onClick={() => setActiveId(feature.id)}
|
||||||
|
onKeyDown={onTabKeyDown}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex items-center gap-2 rounded-full border px-3 py-1.5 text-sm font-medium transition-all",
|
"focus-visible:ring-primary/40 inline-flex min-h-11 shrink-0 items-center gap-2 rounded-full px-4 py-2 text-sm font-semibold transition-colors outline-none focus-visible:ring-2",
|
||||||
isActive
|
isActive
|
||||||
? "border-primary-button bg-primary-button text-white shadow-sm"
|
? "bg-primary-button text-white shadow-sm"
|
||||||
: "border-border-soft bg-surface text-muted-foreground hover:border-primary/20 hover:text-foreground",
|
: "bg-surface/75 text-muted-foreground hover:bg-surface-soft hover:text-foreground backdrop-blur-xl",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Icon className="size-4" />
|
<Icon className="size-4" aria-hidden="true" />
|
||||||
{feature.label}
|
{feature.label}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div
|
||||||
<h3 className="font-serif text-2xl font-semibold tracking-tight text-foreground sm:text-3xl">
|
id={`feature-panel-${active.id}`}
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby={`feature-tab-${active.id}`}
|
||||||
|
tabIndex={0}
|
||||||
|
className="focus-visible:ring-primary/40 rounded-xl outline-none focus-visible:ring-2"
|
||||||
|
>
|
||||||
|
<p className="text-primary text-xs font-bold tracking-[0.14em] uppercase">
|
||||||
|
Medscribe
|
||||||
|
</p>
|
||||||
|
<h3 className="text-foreground mt-3 max-w-xl font-display text-3xl font-semibold tracking-tight sm:text-4xl">
|
||||||
{active.title}
|
{active.title}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="max-w-xl text-sm leading-relaxed text-muted-foreground sm:text-base">
|
<p className="text-muted-foreground mt-4 max-w-xl leading-relaxed">
|
||||||
{active.description}
|
{active.description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul className="grid gap-x-5 gap-y-2.5 sm:grid-cols-2">
|
<ul className="mt-6 grid gap-3 sm:grid-cols-2">
|
||||||
{active.points.map((point) => (
|
{active.points.map((point) => (
|
||||||
<li
|
<li
|
||||||
key={point}
|
key={point}
|
||||||
className="flex items-start gap-2 text-sm leading-snug text-foreground"
|
className="text-foreground flex items-start gap-2.5 text-sm leading-relaxed"
|
||||||
>
|
>
|
||||||
<span className="mt-0.5 flex size-4 shrink-0 items-center justify-center rounded-full bg-primary-light">
|
<span className="bg-primary-light mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full">
|
||||||
<Check className="size-3 text-primary" />
|
<Check className="text-primary size-3" aria-hidden="true" />
|
||||||
</span>
|
</span>
|
||||||
<span>{point}</span>
|
<span>{point}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="justify-self-center lg:justify-self-end">
|
<div className="justify-self-center lg:justify-self-end">
|
||||||
<div className="flex items-center justify-center rounded-2xl border border-border-soft bg-gradient-to-b from-surface-soft to-primary-light/30 p-6 shadow-[0_18px_45px_rgba(15,23,42,0.10)]">
|
<div className="py-2">
|
||||||
<PhoneMockup src={active.image} alt={active.alt} width={188} />
|
<PhoneMockup
|
||||||
|
src={active.image}
|
||||||
|
darkSrc={active.darkImage}
|
||||||
|
alt={active.alt}
|
||||||
|
width={220}
|
||||||
|
/>
|
||||||
|
<p className="text-muted-foreground mt-4 text-center text-xs">
|
||||||
|
Illustrative profile · Medscribe interface
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -55,11 +55,11 @@ export function LoginForm({ redirectTo }: LoginFormProps) {
|
|||||||
<div className="relative flex min-h-screen items-center justify-center px-4 py-16">
|
<div className="relative flex min-h-screen items-center justify-center px-4 py-16">
|
||||||
<ScreenBackground />
|
<ScreenBackground />
|
||||||
|
|
||||||
<Card className="w-full max-w-md border-border-soft/80 bg-surface/95 shadow-[0_20px_50px_rgba(15,23,42,0.12)] backdrop-blur-sm">
|
<Card className="w-full max-w-md bg-surface/95 shadow-[0_20px_50px_rgba(15,23,42,0.12)] backdrop-blur-sm">
|
||||||
<CardHeader className="items-center space-y-4 text-center">
|
<CardHeader className="items-center space-y-4 text-center">
|
||||||
<MedscribeLogo className="h-8 w-[100px] text-foreground" />
|
<MedscribeLogo className="h-8 w-[100px] text-foreground" />
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<CardTitle className="font-serif text-2xl">
|
<CardTitle className="font-display text-2xl">
|
||||||
Enter site password
|
Enter site password
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
@@ -81,7 +81,6 @@ export function LoginForm({ redirectTo }: LoginFormProps) {
|
|||||||
name="password"
|
name="password"
|
||||||
type="password"
|
type="password"
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
autoFocus
|
|
||||||
required
|
required
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(event) => setPassword(event.target.value)}
|
onChange={(event) => setPassword(event.target.value)}
|
||||||
@@ -103,7 +102,7 @@ export function LoginForm({ redirectTo }: LoginFormProps) {
|
|||||||
className="w-full"
|
className="w-full"
|
||||||
disabled={isSubmitting || password.length === 0}
|
disabled={isSubmitting || password.length === 0}
|
||||||
>
|
>
|
||||||
{isSubmitting ? "Checking..." : "Continue"}
|
{isSubmitting ? "Checking…" : "Continue"}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||