evaldoc 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/.env.staging.example +19 -0
  2. package/.firebaserc +6 -0
  3. package/.github/workflows/auto-merge-to-main.yml +28 -0
  4. package/.github/workflows/ci.yml +27 -0
  5. package/.github/workflows/npxhub-publish.yml +170 -0
  6. package/.prettierignore +4 -0
  7. package/.prettierrc.json +7 -0
  8. package/README.md +142 -0
  9. package/SRS.md +448 -0
  10. package/apphosting.staging.yaml +48 -0
  11. package/apphosting.yaml +50 -0
  12. package/bin/evaldoc.cjs +105 -0
  13. package/cors.json +8 -0
  14. package/docs/ARCHITECTURE.md +769 -0
  15. package/docs/ARCHITECTURE_SUMMARY.md +323 -0
  16. package/docs/DEPLOYMENT.md +86 -0
  17. package/docs/DEPLOYMENT_STEPS.md +373 -0
  18. package/docs/DEPLOY_FIRESTORE_RULES.md +239 -0
  19. package/docs/DEPLOY_STORAGE_RULES.md +82 -0
  20. package/docs/DOCTOR_TUTORIAL.md +467 -0
  21. package/docs/FIREBASE_STORAGE_CORS_FIX.md +131 -0
  22. package/docs/FIXES_APPLIED.md +128 -0
  23. package/docs/IMPLEMENTATION.md +498 -0
  24. package/docs/IMPLEMENTATION_COMPLETE.md +317 -0
  25. package/docs/LLM-SETUP.md +216 -0
  26. package/docs/MANUAL_TEST_GUIDE.md +327 -0
  27. package/docs/PATIENT_GUIDE.md +451 -0
  28. package/docs/PLATFORM_OVERVIEW.md +194 -0
  29. package/docs/QUICKSTART.md +252 -0
  30. package/docs/QUICK_REFERENCE.md +222 -0
  31. package/docs/READY_TO_TEST.md +383 -0
  32. package/docs/RUN.md +237 -0
  33. package/docs/START_TESTING.md +130 -0
  34. package/docs/STRIPE_CONNECT_SETUP.md +454 -0
  35. package/docs/SYSTEM_READY.md +345 -0
  36. package/docs/THREADS_IMPLEMENTATION_COMPLETE.md +431 -0
  37. package/docs/THREAD_IMPLEMENTATION_GUIDE.md +345 -0
  38. package/docs/firebasefiles.md +21 -0
  39. package/docs/homepage.md +1208 -0
  40. package/docs/payments.md +159 -0
  41. package/eslint.config.js +76 -0
  42. package/firebase.json +27 -0
  43. package/firestore.indexes.json +88 -0
  44. package/firestore.rules +125 -0
  45. package/functions/package-lock.json +2884 -0
  46. package/functions/package.json +20 -0
  47. package/functions/src/analytics.js +101 -0
  48. package/functions/src/audit.js +28 -0
  49. package/functions/src/auth.js +19 -0
  50. package/functions/src/drafts.js +128 -0
  51. package/functions/src/feedback.js +164 -0
  52. package/functions/src/firebase-init.js +29 -0
  53. package/functions/src/i18n.js +134 -0
  54. package/functions/src/limits.js +27 -0
  55. package/functions/src/llm.js +271 -0
  56. package/functions/src/ner.js +162 -0
  57. package/functions/src/notifications.js +273 -0
  58. package/functions/src/orgs.js +127 -0
  59. package/functions/src/patient-api.js +545 -0
  60. package/functions/src/payment.js +830 -0
  61. package/functions/src/safety.js +188 -0
  62. package/functions/src/stripe-config.js +33 -0
  63. package/functions/src/topics.js +94 -0
  64. package/functions/src/triage.js +191 -0
  65. package/functions/src/utils.js +28 -0
  66. package/functions/src/validate.js +177 -0
  67. package/functions/test-firebase.js +36 -0
  68. package/jest.config.js +18 -0
  69. package/package.json +52 -0
  70. package/public/404.html +93 -0
  71. package/public/app/analytics.html +336 -0
  72. package/public/app/analytics.js +177 -0
  73. package/public/app/app.css +1400 -0
  74. package/public/app/app.js +1754 -0
  75. package/public/app/config.js +14 -0
  76. package/public/app/inbox-threads.js +843 -0
  77. package/public/app/inbox.html +598 -0
  78. package/public/app/inbox.js +397 -0
  79. package/public/app/index.html +612 -0
  80. package/public/assets/logo.jpg +0 -0
  81. package/public/chat.html +137 -0
  82. package/public/css/chat.css +870 -0
  83. package/public/css/landing-enhance.css +298 -0
  84. package/public/css/styles.css +1198 -0
  85. package/public/favicon.svg +5 -0
  86. package/public/find-doctor.html +220 -0
  87. package/public/index.html +399 -0
  88. package/public/js/chat-threads.js +513 -0
  89. package/public/js/chat.js +832 -0
  90. package/public/js/search.js +41 -0
  91. package/public/privacy.html +101 -0
  92. package/public/robots.txt +6 -0
  93. package/public/site.webmanifest +12 -0
  94. package/public/terms.html +110 -0
  95. package/scripts/create-test-provider.js +55 -0
  96. package/scripts/deploy-staging.sh +51 -0
  97. package/scripts/fix-provider.js +39 -0
  98. package/scripts/quick-test.sh +37 -0
  99. package/scripts/test-chat.sh +54 -0
  100. package/scripts/test-multi-turn.js +137 -0
  101. package/scripts/test-ner-simple.js +28 -0
  102. package/scripts/test-ner.js +60 -0
  103. package/scripts/test-stripe-connect.js +304 -0
  104. package/scripts/test-webhook-secret.sh +24 -0
  105. package/server.js +3864 -0
  106. package/storage.rules +22 -0
  107. package/test-api.json +1 -0
  108. package/tests/emulator/README.md +38 -0
  109. package/tests/emulator/counters.emulator.test.js +81 -0
  110. package/tests/integration/api-account-selfserve.test.js +312 -0
  111. package/tests/integration/api-admin-digest.test.js +202 -0
  112. package/tests/integration/api-chat-satisfaction.test.js +259 -0
  113. package/tests/integration/api-doctor-drafts.test.js +236 -0
  114. package/tests/integration/api-doctor-inbox.test.js +401 -0
  115. package/tests/integration/api-org.test.js +328 -0
  116. package/tests/integration/api-payment.test.js +664 -0
  117. package/tests/integration/api-provider-settings.test.js +475 -0
  118. package/tests/integration/api-qr.test.js +121 -0
  119. package/tests/integration/api-security.test.js +241 -0
  120. package/tests/integration/api-stripe-search.test.js +303 -0
  121. package/tests/integration/api-webhook-deletion.test.js +231 -0
  122. package/tests/mocks/firebase-admin.js +220 -0
  123. package/tests/unit/analytics.test.js +151 -0
  124. package/tests/unit/audit-bugs.test.js +203 -0
  125. package/tests/unit/chat-ui-logic.test.js +283 -0
  126. package/tests/unit/drafts.test.js +104 -0
  127. package/tests/unit/feedback.test.js +148 -0
  128. package/tests/unit/i18n.test.js +229 -0
  129. package/tests/unit/llm-retry.test.js +122 -0
  130. package/tests/unit/notifications.test.js +155 -0
  131. package/tests/unit/orgs.test.js +328 -0
  132. package/tests/unit/payment.test.js +889 -0
  133. package/tests/unit/safety-edge-cases.test.js +509 -0
  134. package/tests/unit/safety-srs-compliance.test.js +252 -0
  135. package/tests/unit/safety.test.js +291 -0
  136. package/tests/unit/server-helpers.test.js +774 -0
  137. package/tests/unit/topics-limits.test.js +158 -0
  138. package/tests/unit/triage.test.js +152 -0
  139. package/tests/unit/validate.test.js +408 -0
@@ -0,0 +1,298 @@
1
+ /* ============================================================
2
+ EvalDoc landing — ThreeUI-inspired enhancement layer
3
+ Additive, scoped under body.landing-v2 so it cannot affect
4
+ other pages that share styles.css (e.g. find-doctor.html).
5
+ Aesthetic: light-first glassmorphism, soft animated gradient
6
+ mesh, layered depth + glow. Theme-aware. Motion is fully
7
+ gated behind prefers-reduced-motion.
8
+ ============================================================ */
9
+
10
+ body.landing-v2 {
11
+ --glass-bg: rgba(255, 255, 255, 0.62);
12
+ --glass-brd: rgba(255, 255, 255, 0.75);
13
+ --glass-brd-strong: rgba(41, 123, 86, 0.22);
14
+ --glow-a: rgba(58, 157, 111, 0.30); /* mint green */
15
+ --glow-b: rgba(41, 123, 86, 0.24); /* deep green */
16
+ --glow-c: rgba(79, 179, 164, 0.28); /* teal */
17
+ --mesh-1: rgba(79, 179, 164, 0.34);
18
+ --mesh-2: rgba(58, 157, 111, 0.30);
19
+ --mesh-3: rgba(148, 209, 178, 0.30);
20
+ --ink: #10221a;
21
+ --ink-soft: #45564d;
22
+ --card-shadow: 0 18px 44px -22px rgba(16, 44, 30, 0.42), 0 6px 16px -12px rgba(16, 44, 30, 0.28);
23
+ --card-shadow-hover: 0 30px 66px -26px rgba(16, 44, 30, 0.5), 0 10px 24px -12px rgba(41, 123, 86, 0.3);
24
+ background: #f4f7f4;
25
+ }
26
+
27
+ /* Note: the base styles.css is light-only (dark text baked in), so this
28
+ enhancement is intentionally light-first in both color schemes to avoid
29
+ low-contrast text. A true dark mode would require theming styles.css too. */
30
+
31
+ /* ---------- HERO: immersive gradient-mesh ---------- */
32
+ body.landing-v2 .hero {
33
+ position: relative;
34
+ isolation: isolate;
35
+ overflow: hidden;
36
+ padding: 6.5rem 0 7rem;
37
+ background: radial-gradient(120% 90% at 50% -10%, #ffffff 0%, #eef4f0 55%, #e6efe9 100%);
38
+ }
39
+
40
+ /* drifting colored blobs */
41
+ body.landing-v2 .hero::before {
42
+ content: "";
43
+ position: absolute;
44
+ inset: -25% -10% -10%;
45
+ z-index: -2;
46
+ background:
47
+ radial-gradient(38% 40% at 18% 28%, var(--mesh-1) 0%, transparent 60%),
48
+ radial-gradient(34% 38% at 82% 22%, var(--mesh-2) 0%, transparent 62%),
49
+ radial-gradient(46% 44% at 62% 88%, var(--mesh-3) 0%, transparent 60%);
50
+ filter: blur(28px) saturate(1.05);
51
+ animation: ev-mesh 26s ease-in-out infinite alternate;
52
+ will-change: transform;
53
+ }
54
+ /* fine grain + soft grid for texture */
55
+ body.landing-v2 .hero::after {
56
+ content: "";
57
+ position: absolute;
58
+ inset: 0;
59
+ z-index: -1;
60
+ background-image:
61
+ linear-gradient(rgba(41, 123, 86, 0.05) 1px, transparent 1px),
62
+ linear-gradient(90deg, rgba(41, 123, 86, 0.05) 1px, transparent 1px);
63
+ background-size: 46px 46px;
64
+ -webkit-mask-image: radial-gradient(70% 60% at 50% 40%, #000 0%, transparent 78%);
65
+ mask-image: radial-gradient(70% 60% at 50% 40%, #000 0%, transparent 78%);
66
+ opacity: 0.7;
67
+ pointer-events: none;
68
+ }
69
+ @keyframes ev-mesh {
70
+ 0% { transform: translate3d(-2%, -1%, 0) rotate(0deg) scale(1.02); }
71
+ 50% { transform: translate3d(2%, 2%, 0) rotate(4deg) scale(1.08); }
72
+ 100% { transform: translate3d(1%, -2%, 0) rotate(-3deg) scale(1.04); }
73
+ }
74
+
75
+ /* hero typography polish */
76
+ body.landing-v2 .hero h1 {
77
+ letter-spacing: -0.035em;
78
+ line-height: 1.04;
79
+ }
80
+ body.landing-v2 .hero h1 br + *,
81
+ body.landing-v2 .hero-content h1 {
82
+ color: var(--ink);
83
+ }
84
+ /* gradient-glow accent on the middle line via background-clip trick:
85
+ applied to whole H1 second line is hard w/o markup, so accent the badge + trust line instead */
86
+
87
+ body.landing-v2 .hero-badge {
88
+ display: inline-block;
89
+ background: var(--glass-bg);
90
+ -webkit-backdrop-filter: blur(14px);
91
+ backdrop-filter: blur(14px);
92
+ border: 1px solid var(--glass-brd-strong);
93
+ border-radius: 999px;
94
+ padding: 0.6rem 1.15rem;
95
+ color: var(--ink-soft);
96
+ box-shadow: 0 0 0 4px rgba(41, 123, 86, 0.06), 0 10px 24px -14px rgba(41, 123, 86, 0.5);
97
+ position: relative;
98
+ }
99
+ body.landing-v2 .hero-badge::before {
100
+ content: "";
101
+ display: inline-block;
102
+ width: 7px; height: 7px;
103
+ border-radius: 50%;
104
+ background: #3a9d6f;
105
+ margin-right: 8px;
106
+ vertical-align: middle;
107
+ box-shadow: 0 0 0 3px rgba(58, 157, 111, 0.22), 0 0 10px 1px rgba(58, 157, 111, 0.7);
108
+ animation: ev-pulse 2.4s ease-in-out infinite;
109
+ }
110
+ @keyframes ev-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.45; } }
111
+
112
+ body.landing-v2 .hero-subtitle { color: var(--ink-soft); }
113
+
114
+ /* glass search box */
115
+ body.landing-v2 .search-box-hero {
116
+ background: var(--glass-bg);
117
+ -webkit-backdrop-filter: blur(18px) saturate(1.2);
118
+ backdrop-filter: blur(18px) saturate(1.2);
119
+ border: 1px solid var(--glass-brd);
120
+ border-radius: 18px;
121
+ box-shadow: var(--card-shadow);
122
+ }
123
+ body.landing-v2 .hero-trust-line {
124
+ color: var(--ink-soft);
125
+ font-weight: 600;
126
+ }
127
+
128
+ /* ---------- Floating glass visual card ---------- */
129
+ body.landing-v2 .hero-visual { perspective: 1200px; }
130
+ body.landing-v2 .visual-card {
131
+ background: var(--glass-bg);
132
+ -webkit-backdrop-filter: blur(22px) saturate(1.25);
133
+ backdrop-filter: blur(22px) saturate(1.25);
134
+ border: 1px solid var(--glass-brd);
135
+ border-radius: 22px;
136
+ box-shadow: var(--card-shadow), inset 0 1px 0 rgba(255,255,255,0.5);
137
+ animation: ev-float 7s ease-in-out infinite;
138
+ will-change: transform;
139
+ }
140
+ @keyframes ev-float {
141
+ 0%,100% { transform: translateY(0) rotateX(0) rotateY(0); }
142
+ 50% { transform: translateY(-12px) rotateX(1.5deg) rotateY(-1.5deg); }
143
+ }
144
+ body.landing-v2 .status-indicator {
145
+ text-shadow: 0 0 10px rgba(58, 157, 111, 0.7);
146
+ }
147
+
148
+ /* ---------- Shared glass-card treatment ---------- */
149
+ body.landing-v2 .feature-card,
150
+ body.landing-v2 .who-card,
151
+ body.landing-v2 .model-card,
152
+ body.landing-v2 .platform-feature,
153
+ body.landing-v2 .problem-col {
154
+ background: var(--glass-bg);
155
+ -webkit-backdrop-filter: blur(16px) saturate(1.15);
156
+ backdrop-filter: blur(16px) saturate(1.15);
157
+ border: 1px solid var(--glass-brd);
158
+ border-radius: 20px;
159
+ box-shadow: var(--card-shadow);
160
+ transition: transform 0.35s cubic-bezier(.2,.7,.2,1), box-shadow 0.35s ease, border-color 0.35s ease;
161
+ }
162
+ body.landing-v2 .feature-card:hover,
163
+ body.landing-v2 .who-card:hover,
164
+ body.landing-v2 .model-card:hover,
165
+ body.landing-v2 .platform-feature:hover {
166
+ transform: translateY(-6px);
167
+ box-shadow: var(--card-shadow-hover);
168
+ border-color: var(--glass-brd-strong);
169
+ }
170
+ /* glow ring behind the emoji tiles */
171
+ body.landing-v2 .feature-icon-large,
172
+ body.landing-v2 .platform-icon,
173
+ body.landing-v2 .model-icon {
174
+ filter: drop-shadow(0 6px 16px rgba(41, 123, 86, 0.35));
175
+ }
176
+
177
+ /* ---------- Section rhythm + heading accent ---------- */
178
+ body.landing-v2 .section h2,
179
+ body.landing-v2 .final-cta h2 {
180
+ letter-spacing: -0.025em;
181
+ }
182
+ body.landing-v2 .section > h2::after {
183
+ content: "";
184
+ display: block;
185
+ width: 54px; height: 3px;
186
+ margin: 0.85rem auto 0;
187
+ border-radius: 3px;
188
+ background: linear-gradient(90deg, #4fb3a4, #297B56);
189
+ box-shadow: 0 0 14px rgba(79, 179, 164, 0.6);
190
+ }
191
+
192
+ /* platform cards live on the dark section — use dark glass so their
193
+ light text stays readable (override the light-glass default above). */
194
+ body.landing-v2 .platform-feature {
195
+ background: rgba(255, 255, 255, 0.07);
196
+ border: 1px solid rgba(255, 255, 255, 0.16);
197
+ box-shadow: 0 18px 40px -22px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.12);
198
+ }
199
+ body.landing-v2 .platform-feature:hover {
200
+ background: rgba(255, 255, 255, 0.11);
201
+ border-color: rgba(120, 210, 180, 0.4);
202
+ box-shadow: 0 26px 56px -22px rgba(0,0,0,0.68), 0 0 0 1px rgba(120,210,180,0.25);
203
+ }
204
+
205
+ /* dark sections keep their identity but gain glow depth */
206
+ body.landing-v2 .section-dark {
207
+ position: relative;
208
+ overflow: hidden;
209
+ }
210
+ body.landing-v2 .section-dark::before {
211
+ content: "";
212
+ position: absolute; inset: 0; z-index: 0;
213
+ background:
214
+ radial-gradient(40% 60% at 12% 20%, rgba(79,179,164,0.18) 0%, transparent 60%),
215
+ radial-gradient(46% 66% at 88% 90%, rgba(58,157,111,0.16) 0%, transparent 62%);
216
+ pointer-events: none;
217
+ }
218
+ body.landing-v2 .section-dark > * { position: relative; z-index: 1; }
219
+
220
+ /* ---------- Flow diagram: glowing nodes ---------- */
221
+ body.landing-v2 .flow-number {
222
+ background: linear-gradient(150deg, #3a9d6f, #1e5c40);
223
+ box-shadow: 0 8px 22px -8px rgba(41, 123, 86, 0.7), 0 0 0 5px rgba(58, 157, 111, 0.1);
224
+ }
225
+ body.landing-v2 .flow-arrow { color: #4fb3a4; opacity: 0.8; }
226
+
227
+ /* ---------- Buttons: gradient + glow ---------- */
228
+ body.landing-v2 .btn-large-primary,
229
+ body.landing-v2 .nav-cta,
230
+ body.landing-v2 .search-form button {
231
+ background: linear-gradient(135deg, #3a9d6f 0%, #297B56 60%, #1e5c40 100%);
232
+ border: none;
233
+ box-shadow: 0 12px 28px -12px rgba(41, 123, 86, 0.8), inset 0 1px 0 rgba(255,255,255,0.25);
234
+ transition: transform 0.25s ease, box-shadow 0.25s ease, filter 0.25s ease;
235
+ }
236
+ body.landing-v2 .btn-large-primary:hover,
237
+ body.landing-v2 .nav-cta:hover,
238
+ body.landing-v2 .search-form button:hover {
239
+ transform: translateY(-2px);
240
+ filter: brightness(1.05);
241
+ box-shadow: 0 18px 38px -12px rgba(41, 123, 86, 0.9), 0 0 0 4px rgba(58,157,111,0.14);
242
+ }
243
+ body.landing-v2 .btn-large-secondary,
244
+ body.landing-v2 .btn-secondary {
245
+ background: var(--glass-bg);
246
+ -webkit-backdrop-filter: blur(12px);
247
+ backdrop-filter: blur(12px);
248
+ border: 1px solid var(--glass-brd-strong);
249
+ }
250
+
251
+ /* ---------- Context strip + feyti + final CTA depth ---------- */
252
+ body.landing-v2 .final-cta {
253
+ position: relative;
254
+ overflow: hidden;
255
+ isolation: isolate;
256
+ }
257
+ body.landing-v2 .final-cta::before {
258
+ content: "";
259
+ position: absolute; inset: 0; z-index: -1;
260
+ background:
261
+ radial-gradient(50% 80% at 20% 10%, var(--glow-c) 0%, transparent 60%),
262
+ radial-gradient(50% 80% at 85% 100%, var(--glow-a) 0%, transparent 62%);
263
+ filter: blur(10px);
264
+ }
265
+ body.landing-v2 .waitlist-launch-badge {
266
+ box-shadow: 0 0 0 4px rgba(58,157,111,0.12), 0 8px 20px -10px rgba(41,123,86,0.6);
267
+ }
268
+
269
+ /* model "Pro" featured card lift */
270
+ body.landing-v2 .model-card[style*="border"] {
271
+ box-shadow: 0 24px 54px -22px rgba(41, 123, 86, 0.55), 0 0 0 1px rgba(79,179,164,0.3) !important;
272
+ }
273
+
274
+ /* ---------- Respect reduced motion ---------- */
275
+ @media (prefers-reduced-motion: reduce) {
276
+ body.landing-v2 .hero::before,
277
+ body.landing-v2 .visual-card,
278
+ body.landing-v2 .hero-badge::before {
279
+ animation: none !important;
280
+ }
281
+ body.landing-v2 .feature-card,
282
+ body.landing-v2 .who-card,
283
+ body.landing-v2 .model-card,
284
+ body.landing-v2 .platform-feature,
285
+ body.landing-v2 .btn-large-primary,
286
+ body.landing-v2 .nav-cta { transition: none !important; }
287
+ }
288
+
289
+ /* ---------- Graceful fallback where backdrop-filter is unsupported ---------- */
290
+ @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
291
+ body.landing-v2 .feature-card,
292
+ body.landing-v2 .who-card,
293
+ body.landing-v2 .model-card,
294
+ body.landing-v2 .problem-col,
295
+ body.landing-v2 .visual-card,
296
+ body.landing-v2 .search-box-hero { background: rgba(255,255,255,0.94); }
297
+ body.landing-v2 .platform-feature { background: rgba(255,255,255,0.09); }
298
+ }