@ucptools/validator 1.0.0 → 1.0.1

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 (236) hide show
  1. package/.claude/settings.local.json +60 -0
  2. package/.vercel/README.txt +11 -0
  3. package/.vercel/project.json +1 -0
  4. package/dist/cli/index.d.ts +6 -0
  5. package/dist/cli/index.d.ts.map +1 -0
  6. package/dist/cli/index.js +279 -0
  7. package/dist/cli/index.js.map +1 -0
  8. package/dist/compliance/compliance-generator.d.ts +34 -0
  9. package/dist/compliance/compliance-generator.d.ts.map +1 -0
  10. package/dist/compliance/compliance-generator.js +320 -0
  11. package/dist/compliance/compliance-generator.js.map +1 -0
  12. package/dist/compliance/index.d.ts +8 -0
  13. package/dist/compliance/index.d.ts.map +1 -0
  14. package/dist/compliance/index.js +17 -0
  15. package/dist/compliance/index.js.map +1 -0
  16. package/dist/compliance/templates.d.ts +34 -0
  17. package/dist/compliance/templates.d.ts.map +1 -0
  18. package/{src/compliance/templates.ts → dist/compliance/templates.js} +117 -155
  19. package/dist/compliance/templates.js.map +1 -0
  20. package/dist/compliance/types.d.ts +64 -0
  21. package/dist/compliance/types.d.ts.map +1 -0
  22. package/dist/compliance/types.js +64 -0
  23. package/dist/compliance/types.js.map +1 -0
  24. package/dist/db/index.d.ts +11 -0
  25. package/dist/db/index.d.ts.map +1 -0
  26. package/dist/db/index.js +63 -0
  27. package/dist/db/index.js.map +1 -0
  28. package/dist/db/schema.d.ts +444 -0
  29. package/dist/db/schema.d.ts.map +1 -0
  30. package/dist/db/schema.js +65 -0
  31. package/dist/db/schema.js.map +1 -0
  32. package/dist/feed-analyzer/feed-analyzer.d.ts +26 -0
  33. package/dist/feed-analyzer/feed-analyzer.d.ts.map +1 -0
  34. package/{src/feed-analyzer/feed-analyzer.ts → dist/feed-analyzer/feed-analyzer.js} +642 -726
  35. package/dist/feed-analyzer/feed-analyzer.js.map +1 -0
  36. package/dist/feed-analyzer/index.d.ts +8 -0
  37. package/dist/feed-analyzer/index.d.ts.map +1 -0
  38. package/dist/feed-analyzer/index.js +19 -0
  39. package/dist/feed-analyzer/index.js.map +1 -0
  40. package/dist/feed-analyzer/types.d.ts +204 -0
  41. package/dist/feed-analyzer/types.d.ts.map +1 -0
  42. package/dist/feed-analyzer/types.js +162 -0
  43. package/dist/feed-analyzer/types.js.map +1 -0
  44. package/{src/generator/index.ts → dist/generator/index.d.ts} +1 -1
  45. package/dist/generator/index.d.ts.map +1 -0
  46. package/dist/generator/index.js +13 -0
  47. package/dist/generator/index.js.map +1 -0
  48. package/dist/generator/key-generator.d.ts +24 -0
  49. package/dist/generator/key-generator.d.ts.map +1 -0
  50. package/dist/generator/key-generator.js +144 -0
  51. package/dist/generator/key-generator.js.map +1 -0
  52. package/dist/generator/profile-builder.d.ts +15 -0
  53. package/dist/generator/profile-builder.d.ts.map +1 -0
  54. package/dist/generator/profile-builder.js +338 -0
  55. package/dist/generator/profile-builder.js.map +1 -0
  56. package/dist/hosting/artifacts-generator.d.ts +10 -0
  57. package/dist/hosting/artifacts-generator.d.ts.map +1 -0
  58. package/{src/hosting/artifacts-generator.ts → dist/hosting/artifacts-generator.js} +191 -241
  59. package/dist/hosting/artifacts-generator.js.map +1 -0
  60. package/{src/hosting/index.ts → dist/hosting/index.d.ts} +1 -1
  61. package/dist/hosting/index.d.ts.map +1 -0
  62. package/dist/hosting/index.js +10 -0
  63. package/dist/hosting/index.js.map +1 -0
  64. package/dist/index.d.ts +18 -0
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.js +78 -0
  67. package/dist/index.js.map +1 -0
  68. package/{src/security/index.ts → dist/security/index.d.ts} +8 -15
  69. package/dist/security/index.d.ts.map +1 -0
  70. package/dist/security/index.js +12 -0
  71. package/dist/security/index.js.map +1 -0
  72. package/dist/security/security-scanner.d.ts +10 -0
  73. package/dist/security/security-scanner.d.ts.map +1 -0
  74. package/dist/security/security-scanner.js +541 -0
  75. package/dist/security/security-scanner.js.map +1 -0
  76. package/dist/security/types.d.ts +48 -0
  77. package/dist/security/types.d.ts.map +1 -0
  78. package/dist/security/types.js +21 -0
  79. package/dist/security/types.js.map +1 -0
  80. package/dist/services/directory.d.ts +104 -0
  81. package/dist/services/directory.d.ts.map +1 -0
  82. package/dist/services/directory.js +333 -0
  83. package/dist/services/directory.js.map +1 -0
  84. package/dist/simulator/agent-simulator.d.ts +69 -0
  85. package/dist/simulator/agent-simulator.d.ts.map +1 -0
  86. package/{src/simulator/agent-simulator.ts → dist/simulator/agent-simulator.js} +650 -941
  87. package/dist/simulator/agent-simulator.js.map +1 -0
  88. package/{src/simulator/index.ts → dist/simulator/index.d.ts} +7 -7
  89. package/dist/simulator/index.d.ts.map +1 -0
  90. package/dist/simulator/index.js +23 -0
  91. package/dist/simulator/index.js.map +1 -0
  92. package/{src/simulator/types.ts → dist/simulator/types.d.ts} +145 -170
  93. package/dist/simulator/types.d.ts.map +1 -0
  94. package/dist/simulator/types.js +18 -0
  95. package/dist/simulator/types.js.map +1 -0
  96. package/dist/types/generator.d.ts +106 -0
  97. package/dist/types/generator.d.ts.map +1 -0
  98. package/dist/types/generator.js +6 -0
  99. package/dist/types/generator.js.map +1 -0
  100. package/{src/types/index.ts → dist/types/index.d.ts} +1 -1
  101. package/dist/types/index.d.ts.map +1 -0
  102. package/dist/types/index.js +23 -0
  103. package/dist/types/index.js.map +1 -0
  104. package/dist/types/ucp-profile.d.ts +103 -0
  105. package/dist/types/ucp-profile.d.ts.map +1 -0
  106. package/dist/types/ucp-profile.js +45 -0
  107. package/dist/types/ucp-profile.js.map +1 -0
  108. package/dist/types/validation.d.ts +68 -0
  109. package/dist/types/validation.d.ts.map +1 -0
  110. package/dist/types/validation.js +32 -0
  111. package/dist/types/validation.js.map +1 -0
  112. package/dist/validator/index.d.ts +26 -0
  113. package/dist/validator/index.d.ts.map +1 -0
  114. package/dist/validator/index.js +161 -0
  115. package/dist/validator/index.js.map +1 -0
  116. package/dist/validator/network-validator.d.ts +28 -0
  117. package/dist/validator/network-validator.d.ts.map +1 -0
  118. package/dist/validator/network-validator.js +319 -0
  119. package/dist/validator/network-validator.js.map +1 -0
  120. package/dist/validator/rules-validator.d.ts +11 -0
  121. package/dist/validator/rules-validator.d.ts.map +1 -0
  122. package/dist/validator/rules-validator.js +257 -0
  123. package/dist/validator/rules-validator.js.map +1 -0
  124. package/dist/validator/sdk-validator.d.ts +58 -0
  125. package/dist/validator/sdk-validator.d.ts.map +1 -0
  126. package/{src/validator/sdk-validator.ts → dist/validator/sdk-validator.js} +273 -330
  127. package/dist/validator/sdk-validator.js.map +1 -0
  128. package/dist/validator/structural-validator.d.ts +11 -0
  129. package/dist/validator/structural-validator.d.ts.map +1 -0
  130. package/dist/validator/structural-validator.js +415 -0
  131. package/dist/validator/structural-validator.js.map +1 -0
  132. package/package.json +1 -1
  133. package/publish-output.txt +0 -0
  134. package/CLAUDE.md +0 -109
  135. package/api/analyze-feed.js +0 -140
  136. package/api/badge.js +0 -185
  137. package/api/benchmark.js +0 -177
  138. package/api/directory-stats.ts +0 -29
  139. package/api/directory.ts +0 -73
  140. package/api/generate-compliance.js +0 -143
  141. package/api/generate-schema.js +0 -457
  142. package/api/generate.js +0 -132
  143. package/api/security-scan.js +0 -133
  144. package/api/simulate.js +0 -187
  145. package/api/tsconfig.json +0 -10
  146. package/api/validate.js +0 -1351
  147. package/apify-actor/.actor/actor.json +0 -68
  148. package/apify-actor/.actor/input_schema.json +0 -32
  149. package/apify-actor/APIFY-STORE-LISTING.md +0 -412
  150. package/apify-actor/Dockerfile +0 -8
  151. package/apify-actor/README.md +0 -166
  152. package/apify-actor/main.ts +0 -111
  153. package/apify-actor/package.json +0 -17
  154. package/apify-actor/src/main.js +0 -199
  155. package/docs/BRAND-IDENTITY.md +0 -238
  156. package/docs/BRAND-STYLE-GUIDE.md +0 -356
  157. package/drizzle/0000_black_king_cobra.sql +0 -39
  158. package/drizzle/meta/0000_snapshot.json +0 -309
  159. package/drizzle/meta/_journal.json +0 -13
  160. package/drizzle.config.ts +0 -10
  161. package/public/.well-known/ucp +0 -25
  162. package/public/android-chrome-192x192.png +0 -0
  163. package/public/android-chrome-512x512.png +0 -0
  164. package/public/apple-touch-icon.png +0 -0
  165. package/public/brand.css +0 -321
  166. package/public/directory.html +0 -701
  167. package/public/favicon-16x16.png +0 -0
  168. package/public/favicon-32x32.png +0 -0
  169. package/public/favicon.ico +0 -0
  170. package/public/guides/bigcommerce.html +0 -743
  171. package/public/guides/fastucp.html +0 -838
  172. package/public/guides/magento.html +0 -779
  173. package/public/guides/shopify.html +0 -726
  174. package/public/guides/squarespace.html +0 -749
  175. package/public/guides/wix.html +0 -747
  176. package/public/guides/woocommerce.html +0 -733
  177. package/public/index.html +0 -3835
  178. package/public/learn.html +0 -396
  179. package/public/logo.jpeg +0 -0
  180. package/public/og-image-icon.png +0 -0
  181. package/public/og-image.png +0 -0
  182. package/public/robots.txt +0 -6
  183. package/public/site.webmanifest +0 -31
  184. package/public/sitemap.xml +0 -69
  185. package/public/social/linkedin-banner-1128x191.png +0 -0
  186. package/public/social/temp.PNG +0 -0
  187. package/public/social/x-header-1500x500.png +0 -0
  188. package/public/verify.html +0 -410
  189. package/scripts/generate-favicons.js +0 -44
  190. package/scripts/generate-ico.js +0 -23
  191. package/scripts/generate-og-image.js +0 -45
  192. package/scripts/reset-db.ts +0 -77
  193. package/scripts/seed-db.ts +0 -71
  194. package/scripts/setup-benchmark-db.js +0 -70
  195. package/src/api/server.ts +0 -266
  196. package/src/cli/index.ts +0 -302
  197. package/src/compliance/compliance-generator.ts +0 -452
  198. package/src/compliance/index.ts +0 -28
  199. package/src/compliance/types.ts +0 -170
  200. package/src/db/index.ts +0 -28
  201. package/src/db/schema.ts +0 -84
  202. package/src/feed-analyzer/index.ts +0 -34
  203. package/src/feed-analyzer/types.ts +0 -354
  204. package/src/generator/key-generator.ts +0 -124
  205. package/src/generator/profile-builder.ts +0 -402
  206. package/src/index.ts +0 -105
  207. package/src/security/security-scanner.ts +0 -604
  208. package/src/security/types.ts +0 -55
  209. package/src/services/directory.ts +0 -434
  210. package/src/types/generator.ts +0 -140
  211. package/src/types/ucp-profile.ts +0 -140
  212. package/src/types/validation.ts +0 -89
  213. package/src/validator/index.ts +0 -194
  214. package/src/validator/network-validator.ts +0 -417
  215. package/src/validator/rules-validator.ts +0 -297
  216. package/src/validator/structural-validator.ts +0 -476
  217. package/tests/fixtures/non-compliant-profile.json +0 -25
  218. package/tests/fixtures/official-sample-profile.json +0 -75
  219. package/tests/integration/benchmark.test.ts +0 -207
  220. package/tests/integration/database.test.ts +0 -163
  221. package/tests/integration/directory-api.test.ts +0 -268
  222. package/tests/integration/simulate-api.test.ts +0 -230
  223. package/tests/integration/validate-api.test.ts +0 -269
  224. package/tests/setup.ts +0 -15
  225. package/tests/unit/agent-simulator.test.ts +0 -575
  226. package/tests/unit/compliance-generator.test.ts +0 -374
  227. package/tests/unit/directory-service.test.ts +0 -272
  228. package/tests/unit/feed-analyzer.test.ts +0 -517
  229. package/tests/unit/lint-suggestions.test.ts +0 -423
  230. package/tests/unit/official-samples.test.ts +0 -211
  231. package/tests/unit/pdf-report.test.ts +0 -390
  232. package/tests/unit/sdk-validator.test.ts +0 -531
  233. package/tests/unit/security-scanner.test.ts +0 -410
  234. package/tests/unit/validation.test.ts +0 -390
  235. package/vercel.json +0 -34
  236. package/vitest.config.ts +0 -22
@@ -1,701 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <!-- Google Analytics -->
5
- <script async src="https://www.googletagmanager.com/gtag/js?id=G-J5JSHV7H1E"></script>
6
- <script>
7
- window.dataLayer = window.dataLayer || [];
8
- function gtag(){dataLayer.push(arguments);}
9
- gtag('js', new Date());
10
- gtag('config', 'G-J5JSHV7H1E');
11
- </script>
12
- <!-- Ahrefs Analytics -->
13
- <script src="https://analytics.ahrefs.com/analytics.js" data-key="w/KDij6w81HzA8oXaw60JA" async></script>
14
- <meta charset="UTF-8">
15
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
16
- <title>UCP Merchant Directory - AI-Ready E-commerce Stores | Universal Commerce Protocol</title>
17
- <meta name="description" content="Browse the public directory of Universal Commerce Protocol (UCP) enabled merchants. Discover AI-ready e-commerce stores compatible with Google AI Mode, ChatGPT Shopping, and Microsoft Copilot checkout.">
18
- <meta name="keywords" content="UCP Directory, AI-Ready Stores, Universal Commerce Protocol Merchants, AI Commerce Directory, UCP Enabled Stores, AI Shopping Compatible">
19
- <meta name="robots" content="index, follow">
20
- <link rel="canonical" href="https://ucptools.dev/directory">
21
- <link rel="icon" type="image/x-icon" href="/favicon.ico">
22
- <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
23
- <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
24
- <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
25
- <meta name="theme-color" content="#2E86AB">
26
-
27
- <!-- Open Graph / Facebook -->
28
- <meta property="og:type" content="website">
29
- <meta property="og:url" content="https://ucptools.dev/directory">
30
- <meta property="og:title" content="UCP Merchant Directory - AI-Ready E-commerce Stores">
31
- <meta property="og:description" content="Browse Universal Commerce Protocol (UCP) enabled merchants ready for AI shopping agents.">
32
- <meta property="og:image" content="https://ucptools.dev/og-image.png">
33
- <meta property="og:site_name" content="UCP.tools">
34
-
35
- <!-- Twitter Card -->
36
- <meta name="twitter:card" content="summary_large_image">
37
- <meta name="twitter:url" content="https://ucptools.dev/directory">
38
- <meta name="twitter:title" content="UCP Merchant Directory - AI-Ready E-commerce Stores">
39
- <meta name="twitter:description" content="Discover UCP-enabled e-commerce stores compatible with AI shopping agents.">
40
- <meta name="twitter:image" content="https://ucptools.dev/og-image.png">
41
-
42
- <!-- JSON-LD Structured Data -->
43
- <script type="application/ld+json">
44
- {
45
- "@context": "https://schema.org",
46
- "@type": "CollectionPage",
47
- "name": "UCP Merchant Directory",
48
- "description": "Directory of Universal Commerce Protocol (UCP) enabled merchants ready for AI commerce.",
49
- "url": "https://ucptools.dev/directory",
50
- "isPartOf": {
51
- "@type": "WebSite",
52
- "name": "UCP.tools",
53
- "url": "https://ucptools.dev/"
54
- },
55
- "breadcrumb": {
56
- "@type": "BreadcrumbList",
57
- "itemListElement": [
58
- {"@type": "ListItem", "position": 1, "name": "Home", "item": "https://ucptools.dev/"},
59
- {"@type": "ListItem", "position": 2, "name": "Merchant Directory", "item": "https://ucptools.dev/directory"}
60
- ]
61
- }
62
- }
63
- </script>
64
- <style>
65
- :root {
66
- --brand-blue: #2E86AB;
67
- --brand-teal: #36B5A2;
68
- --brand-green: #47C97A;
69
- --brand-gradient: linear-gradient(135deg, #2E86AB 0%, #36B5A2 50%, #47C97A 100%);
70
- --brand-gradient-hover: linear-gradient(135deg, #267593 0%, #2EA18F 50%, #3BB86B 100%);
71
- --color-dark: #1A2B3C;
72
- --color-medium: #5A6978;
73
- --color-light: #94A3B8;
74
- --color-border: #E2E8F0;
75
- --color-background: #F8FAFC;
76
- --color-card: #FFFFFF;
77
- --color-success: #47C97A;
78
- --color-warning: #F59E0B;
79
- --color-error: #EF4444;
80
- --color-info: #2E86AB;
81
- --grade-a-bg: #DCFCE7; --grade-a-text: #16A34A;
82
- --grade-b-bg: #DBEAFE; --grade-b-text: #2563EB;
83
- --grade-c-bg: #FEF9C3; --grade-c-text: #CA8A04;
84
- --grade-d-bg: #FED7AA; --grade-d-text: #EA580C;
85
- --grade-f-bg: #FEE2E2; --grade-f-text: #DC2626;
86
- }
87
-
88
- * { box-sizing: border-box; margin: 0; padding: 0; }
89
- body {
90
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
91
- background: var(--color-background);
92
- color: var(--color-dark);
93
- line-height: 1.6;
94
- }
95
- .container { max-width: 1200px; margin: 0 auto; padding: 0 20px; }
96
-
97
- /* Header */
98
- header {
99
- background: var(--color-card);
100
- border-bottom: 1px solid var(--color-border);
101
- padding: 16px 0;
102
- position: sticky;
103
- top: 0;
104
- z-index: 1000;
105
- }
106
- .header-inner { display: flex; justify-content: space-between; align-items: center; }
107
- .logo { display: flex; align-items: center; gap: 12px; font-size: 24px; font-weight: 700; text-decoration: none; }
108
- .logo-icon { width: 40px; height: 40px; border-radius: 8px; }
109
- .logo-text { background: var(--brand-gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
110
- .logo-suffix { font-weight: 400; -webkit-text-fill-color: var(--color-medium); }
111
- nav a { margin-left: 24px; color: var(--color-medium); text-decoration: none; transition: color 0.2s; }
112
- nav a:hover, nav a.active { color: var(--brand-blue); }
113
- /* Dropdown */
114
- .dropdown { position: relative; display: inline-block; margin-left: 24px; }
115
- .dropdown-toggle { color: var(--color-medium); cursor: pointer; background: none; border: none; font-size: inherit; font-family: inherit; padding: 0; transition: color 0.2s; }
116
- .dropdown-toggle:hover, .dropdown:hover .dropdown-toggle { color: var(--brand-blue); }
117
- .dropdown-toggle::after { content: '▼'; font-size: 0.6em; margin-left: 4px; vertical-align: middle; }
118
- .dropdown-menu { display: none; position: absolute; top: 100%; left: 0; background: var(--color-card); border: 1px solid var(--color-border); border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); min-width: 180px; z-index: 1001; padding: 8px 0; margin-top: 8px; }
119
- .dropdown:hover .dropdown-menu { display: block; }
120
- .dropdown-menu a { display: block; padding: 10px 16px; color: var(--color-medium); text-decoration: none; margin-left: 0; }
121
- .dropdown-menu a:hover { background: rgba(46,134,171,0.08); color: var(--brand-blue); }
122
- .dropdown-divider { height: 1px; background: var(--color-border); margin: 8px 0; }
123
-
124
- /* Hero */
125
- .hero {
126
- text-align: center;
127
- padding: 48px 20px;
128
- background: linear-gradient(135deg, rgba(46, 134, 171, 0.1) 0%, rgba(54, 181, 162, 0.05) 50%, rgba(71, 201, 122, 0.1) 100%);
129
- }
130
- .hero h1 { font-size: 36px; margin-bottom: 12px; }
131
- .hero h1 .gradient-text { background: var(--brand-gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
132
- .hero p { font-size: 18px; color: var(--color-medium); max-width: 600px; margin: 0 auto 24px; }
133
-
134
- /* Stats Bar */
135
- .stats-bar {
136
- display: flex;
137
- justify-content: center;
138
- gap: 32px;
139
- flex-wrap: wrap;
140
- }
141
- .stat-item { text-align: center; }
142
- .stat-value { font-size: 28px; font-weight: 700; background: var(--brand-gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
143
- .stat-label { font-size: 14px; color: var(--color-medium); }
144
-
145
- /* Main Content */
146
- .main-content { padding: 40px 0; }
147
- .content-grid { display: grid; grid-template-columns: 280px 1fr; gap: 32px; }
148
- @media (max-width: 900px) { .content-grid { grid-template-columns: 1fr; } }
149
-
150
- /* Sidebar */
151
- .sidebar { display: flex; flex-direction: column; gap: 24px; }
152
- .filter-card {
153
- background: var(--color-card);
154
- border: 1px solid var(--color-border);
155
- border-radius: 12px;
156
- padding: 20px;
157
- }
158
- .filter-card h3 { font-size: 14px; font-weight: 600; color: var(--color-medium); margin-bottom: 12px; text-transform: uppercase; letter-spacing: 0.5px; }
159
- .search-input {
160
- width: 100%;
161
- padding: 12px 16px;
162
- border: 2px solid var(--color-border);
163
- border-radius: 8px;
164
- font-size: 14px;
165
- transition: border-color 0.2s;
166
- }
167
- .search-input:focus { outline: none; border-color: var(--brand-blue); }
168
- .filter-list { list-style: none; }
169
- .filter-item {
170
- display: flex;
171
- align-items: center;
172
- padding: 8px 0;
173
- cursor: pointer;
174
- transition: color 0.2s;
175
- }
176
- .filter-item:hover { color: var(--brand-blue); }
177
- .filter-item input { margin-right: 10px; }
178
- .filter-count { margin-left: auto; font-size: 12px; color: var(--color-light); background: var(--color-background); padding: 2px 8px; border-radius: 10px; }
179
-
180
- /* Submit CTA */
181
- .submit-cta {
182
- background: var(--brand-gradient);
183
- border-radius: 12px;
184
- padding: 20px;
185
- color: white;
186
- text-align: center;
187
- }
188
- .submit-cta h3 { font-size: 16px; margin-bottom: 8px; }
189
- .submit-cta p { font-size: 14px; opacity: 0.9; margin-bottom: 16px; }
190
- .submit-btn {
191
- display: inline-block;
192
- padding: 10px 20px;
193
- background: white;
194
- color: var(--brand-blue);
195
- border-radius: 6px;
196
- font-weight: 600;
197
- text-decoration: none;
198
- transition: transform 0.2s;
199
- }
200
- .submit-btn:hover { transform: translateY(-2px); }
201
-
202
- /* Merchant Grid */
203
- .merchant-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; }
204
- .merchant-card {
205
- background: var(--color-card);
206
- border: 1px solid var(--color-border);
207
- border-radius: 12px;
208
- padding: 20px;
209
- transition: all 0.2s;
210
- text-decoration: none;
211
- color: inherit;
212
- display: block;
213
- }
214
- .merchant-card:hover { border-color: var(--brand-blue); box-shadow: 0 4px 12px rgba(46, 134, 171, 0.15); }
215
- .merchant-header { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
216
- .merchant-logo {
217
- width: 48px;
218
- height: 48px;
219
- border-radius: 8px;
220
- background: var(--color-background);
221
- display: flex;
222
- align-items: center;
223
- justify-content: center;
224
- font-size: 20px;
225
- font-weight: 700;
226
- color: var(--brand-blue);
227
- }
228
- .merchant-logo img { width: 100%; height: 100%; object-fit: cover; border-radius: 8px; }
229
- .merchant-info { flex: 1; min-width: 0; }
230
- .merchant-name { font-weight: 600; font-size: 16px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
231
- .merchant-domain { font-size: 13px; color: var(--color-medium); }
232
- .merchant-grade {
233
- padding: 4px 10px;
234
- border-radius: 6px;
235
- font-weight: 700;
236
- font-size: 14px;
237
- }
238
- .grade-A { background: var(--grade-a-bg); color: var(--grade-a-text); }
239
- .grade-B { background: var(--grade-b-bg); color: var(--grade-b-text); }
240
- .grade-C { background: var(--grade-c-bg); color: var(--grade-c-text); }
241
- .grade-D { background: var(--grade-d-bg); color: var(--grade-d-text); }
242
- .grade-F { background: var(--grade-f-bg); color: var(--grade-f-text); }
243
- .merchant-desc { font-size: 14px; color: var(--color-medium); margin-bottom: 12px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
244
- .merchant-tags { display: flex; flex-wrap: wrap; gap: 6px; }
245
- .merchant-tag { font-size: 11px; padding: 3px 8px; background: var(--color-background); border-radius: 4px; color: var(--color-medium); }
246
- .transport-tag { background: rgba(46, 134, 171, 0.1); color: var(--brand-blue); }
247
-
248
- /* Pagination */
249
- .pagination { display: flex; justify-content: center; align-items: center; gap: 8px; margin-top: 32px; }
250
- .page-btn {
251
- padding: 8px 16px;
252
- border: 1px solid var(--color-border);
253
- background: var(--color-card);
254
- border-radius: 6px;
255
- cursor: pointer;
256
- transition: all 0.2s;
257
- }
258
- .page-btn:hover:not(:disabled) { border-color: var(--brand-blue); color: var(--brand-blue); }
259
- .page-btn:disabled { opacity: 0.5; cursor: not-allowed; }
260
- .page-info { color: var(--color-medium); font-size: 14px; }
261
-
262
- /* Loading & Empty States */
263
- .loading, .empty-state { text-align: center; padding: 60px 20px; color: var(--color-medium); }
264
- .loading::after { content: ''; display: inline-block; width: 24px; height: 24px; border: 3px solid var(--color-border); border-top-color: var(--brand-blue); border-radius: 50%; animation: spin 1s linear infinite; margin-left: 12px; vertical-align: middle; }
265
- @keyframes spin { to { transform: rotate(360deg); } }
266
- .empty-state h3 { font-size: 18px; color: var(--color-dark); margin-bottom: 8px; }
267
-
268
- /* Modal */
269
- .modal-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 2000; align-items: center; justify-content: center; }
270
- .modal-overlay.active { display: flex; }
271
- .modal {
272
- background: var(--color-card);
273
- border-radius: 16px;
274
- padding: 32px;
275
- max-width: 500px;
276
- width: 90%;
277
- max-height: 90vh;
278
- overflow-y: auto;
279
- }
280
- .modal h2 { font-size: 24px; margin-bottom: 8px; }
281
- .modal p.subtitle { color: var(--color-medium); margin-bottom: 24px; }
282
- .form-group { margin-bottom: 16px; }
283
- .form-group label { display: block; font-size: 14px; font-weight: 500; margin-bottom: 6px; }
284
- .form-group input, .form-group select, .form-group textarea {
285
- width: 100%;
286
- padding: 12px;
287
- border: 2px solid var(--color-border);
288
- border-radius: 8px;
289
- font-size: 14px;
290
- }
291
- .form-group input:focus, .form-group select:focus, .form-group textarea:focus { outline: none; border-color: var(--brand-blue); }
292
- .form-actions { display: flex; gap: 12px; margin-top: 24px; }
293
- .btn { padding: 12px 24px; border-radius: 8px; font-weight: 600; cursor: pointer; transition: all 0.2s; border: none; }
294
- .btn-primary { background: var(--brand-gradient); color: white; }
295
- .btn-primary:hover { background: var(--brand-gradient-hover); }
296
- .btn-secondary { background: var(--color-background); color: var(--color-dark); }
297
- .btn-secondary:hover { background: var(--color-border); }
298
- .form-message { padding: 12px; border-radius: 8px; margin-bottom: 16px; font-size: 14px; }
299
- .form-message.error { background: var(--grade-f-bg); color: var(--grade-f-text); }
300
- .form-message.success { background: var(--grade-a-bg); color: var(--grade-a-text); }
301
-
302
- /* Footer */
303
- footer { text-align: center; padding: 40px 20px; color: var(--color-medium); font-size: 14px; border-top: 1px solid var(--color-border); margin-top: 60px; }
304
- footer a { color: var(--brand-blue); text-decoration: none; }
305
- </style>
306
- </head>
307
- <body>
308
- <header>
309
- <div class="container header-inner">
310
- <a href="/" class="logo">
311
- <img src="/logo.jpeg" alt="UCP.tools" class="logo-icon">
312
- <span class="logo-text">UCP<span class="logo-suffix">.tools</span></span>
313
- </a>
314
- <nav>
315
- <a href="/">Validator</a>
316
- <a href="/learn">Learn</a>
317
- <a href="/directory" class="active">Directory</a>
318
- <div class="dropdown">
319
- <span class="dropdown-toggle">Guides</span>
320
- <div class="dropdown-menu">
321
- <a href="/guides/fastucp">⚡ FastUCP</a>
322
- <div class="dropdown-divider"></div>
323
- <a href="/guides/shopify">🛒 Shopify</a>
324
- <a href="/guides/woocommerce">🔌 WooCommerce</a>
325
- <a href="/guides/magento">🧱 Magento</a>
326
- <a href="/guides/wix">✨ Wix</a>
327
- <a href="/guides/bigcommerce">📦 BigCommerce</a>
328
- <a href="/guides/squarespace">◼️ Squarespace</a>
329
- </div>
330
- </div>
331
- </nav>
332
- </div>
333
- </header>
334
-
335
- <section class="hero">
336
- <div class="container">
337
- <h1><span class="gradient-text">UCP Merchant</span> Directory</h1>
338
- <p>Discover stores ready for AI commerce. Browse verified UCP-enabled merchants compatible with AI shopping agents.</p>
339
- <div class="stats-bar" id="stats-bar">
340
- <div class="stat-item">
341
- <div class="stat-value" id="stat-merchants">-</div>
342
- <div class="stat-label">Merchants</div>
343
- </div>
344
- <div class="stat-item">
345
- <div class="stat-value" id="stat-categories">-</div>
346
- <div class="stat-label">Categories</div>
347
- </div>
348
- <div class="stat-item">
349
- <div class="stat-value" id="stat-countries">-</div>
350
- <div class="stat-label">Countries</div>
351
- </div>
352
- <div class="stat-item">
353
- <div class="stat-value" id="stat-avg-score">-</div>
354
- <div class="stat-label">Avg Score</div>
355
- </div>
356
- </div>
357
- </div>
358
- </section>
359
-
360
- <main class="main-content">
361
- <div class="container">
362
- <div class="content-grid">
363
- <aside class="sidebar">
364
- <div class="filter-card">
365
- <h3>Search</h3>
366
- <input type="text" class="search-input" id="search-input" placeholder="Search merchants...">
367
- </div>
368
-
369
- <div class="filter-card">
370
- <h3>Categories</h3>
371
- <ul class="filter-list" id="category-filters">
372
- <li class="filter-item"><span>Loading...</span></li>
373
- </ul>
374
- </div>
375
-
376
- <div class="filter-card">
377
- <h3>Grade</h3>
378
- <ul class="filter-list" id="grade-filters">
379
- <li class="filter-item"><label><input type="checkbox" value="A"> Grade A</label></li>
380
- <li class="filter-item"><label><input type="checkbox" value="B"> Grade B</label></li>
381
- <li class="filter-item"><label><input type="checkbox" value="C"> Grade C</label></li>
382
- <li class="filter-item"><label><input type="checkbox" value="D"> Grade D</label></li>
383
- </ul>
384
- </div>
385
-
386
- <div class="submit-cta">
387
- <h3>List Your Store</h3>
388
- <p>Get discovered by AI agents and join the directory.</p>
389
- <a href="#" class="submit-btn" id="open-submit-modal">Submit Store</a>
390
- </div>
391
- </aside>
392
-
393
- <section class="merchants-section">
394
- <div class="merchant-grid" id="merchant-grid">
395
- <div class="loading">Loading merchants</div>
396
- </div>
397
-
398
- <div class="pagination" id="pagination" style="display: none;">
399
- <button class="page-btn" id="prev-page" disabled>&larr; Previous</button>
400
- <span class="page-info" id="page-info">Page 1 of 1</span>
401
- <button class="page-btn" id="next-page" disabled>Next &rarr;</button>
402
- </div>
403
- </section>
404
- </div>
405
- </div>
406
- </main>
407
-
408
- <!-- Submit Modal -->
409
- <div class="modal-overlay" id="submit-modal">
410
- <div class="modal">
411
- <h2>Submit Your Store</h2>
412
- <p class="subtitle">Add your UCP-enabled store to the directory.</p>
413
-
414
- <div class="form-message" id="form-message" style="display: none;"></div>
415
-
416
- <form id="submit-form">
417
- <div class="form-group">
418
- <label for="submit-domain">Domain *</label>
419
- <input type="text" id="submit-domain" placeholder="example.com" required>
420
- </div>
421
- <div class="form-group">
422
- <label for="submit-name">Display Name</label>
423
- <input type="text" id="submit-name" placeholder="Your Store Name">
424
- </div>
425
- <div class="form-group">
426
- <label for="submit-description">Description</label>
427
- <textarea id="submit-description" rows="3" placeholder="Brief description of your store..."></textarea>
428
- </div>
429
- <div class="form-group">
430
- <label for="submit-category">Category</label>
431
- <select id="submit-category">
432
- <option value="">Select a category</option>
433
- <option value="fashion">Fashion & Apparel</option>
434
- <option value="electronics">Electronics</option>
435
- <option value="home">Home & Garden</option>
436
- <option value="beauty">Beauty & Health</option>
437
- <option value="food">Food & Grocery</option>
438
- <option value="sports">Sports & Outdoors</option>
439
- <option value="toys">Toys & Games</option>
440
- <option value="books">Books & Media</option>
441
- <option value="automotive">Automotive</option>
442
- <option value="other">Other</option>
443
- </select>
444
- </div>
445
- <div class="form-group">
446
- <label for="submit-country">Country</label>
447
- <select id="submit-country">
448
- <option value="">Select a country</option>
449
- <option value="US">United States</option>
450
- <option value="GB">United Kingdom</option>
451
- <option value="DE">Germany</option>
452
- <option value="FR">France</option>
453
- <option value="CA">Canada</option>
454
- <option value="AU">Australia</option>
455
- <option value="NL">Netherlands</option>
456
- <option value="ES">Spain</option>
457
- <option value="IT">Italy</option>
458
- <option value="JP">Japan</option>
459
- </select>
460
- </div>
461
- <div class="form-actions">
462
- <button type="button" class="btn btn-secondary" id="close-modal">Cancel</button>
463
- <button type="submit" class="btn btn-primary" id="submit-btn">Submit Store</button>
464
- </div>
465
- </form>
466
- </div>
467
- </div>
468
-
469
- <footer>
470
- <div class="container">
471
- <p>&copy; 2025 UCP.tools - <a href="https://ucp.dev" target="_blank">Universal Commerce Protocol</a></p>
472
- </div>
473
- </footer>
474
-
475
- <script>
476
- const API_BASE = '/api';
477
- let currentPage = 1;
478
- let totalPages = 1;
479
- let currentFilters = { search: '', category: '', country: '' };
480
-
481
- // Load stats
482
- async function loadStats() {
483
- try {
484
- const res = await fetch(`${API_BASE}/directory-stats`);
485
- const data = await res.json();
486
-
487
- document.getElementById('stat-merchants').textContent = data.stats.totalMerchants;
488
- document.getElementById('stat-categories').textContent = data.stats.totalCategories;
489
- document.getElementById('stat-countries').textContent = data.stats.totalCountries;
490
- document.getElementById('stat-avg-score').textContent = data.stats.avgScore;
491
-
492
- // Update category filters
493
- if (data.topCategories.length > 0) {
494
- const categoryList = document.getElementById('category-filters');
495
- categoryList.innerHTML = data.topCategories.map(cat => `
496
- <li class="filter-item">
497
- <label>
498
- <input type="checkbox" value="${cat.name}">
499
- ${formatCategory(cat.name)}
500
- </label>
501
- <span class="filter-count">${cat.count}</span>
502
- </li>
503
- `).join('');
504
-
505
- // Add event listeners
506
- categoryList.querySelectorAll('input').forEach(input => {
507
- input.addEventListener('change', () => {
508
- currentFilters.category = input.checked ? input.value : '';
509
- currentPage = 1;
510
- loadMerchants();
511
- });
512
- });
513
- }
514
- } catch (error) {
515
- console.error('Failed to load stats:', error);
516
- }
517
- }
518
-
519
- // Load merchants
520
- async function loadMerchants() {
521
- const grid = document.getElementById('merchant-grid');
522
- grid.innerHTML = '<div class="loading">Loading merchants</div>';
523
-
524
- try {
525
- const params = new URLSearchParams({
526
- page: currentPage,
527
- limit: 12,
528
- ...(currentFilters.search && { search: currentFilters.search }),
529
- ...(currentFilters.category && { category: currentFilters.category }),
530
- });
531
-
532
- const res = await fetch(`${API_BASE}/directory?${params}`);
533
- const data = await res.json();
534
-
535
- totalPages = data.pagination.totalPages;
536
-
537
- if (data.merchants.length === 0) {
538
- grid.innerHTML = `
539
- <div class="empty-state">
540
- <h3>No merchants found</h3>
541
- <p>Try adjusting your filters or be the first to submit your store!</p>
542
- </div>
543
- `;
544
- } else {
545
- grid.innerHTML = data.merchants.map(renderMerchantCard).join('');
546
- }
547
-
548
- updatePagination(data.pagination);
549
- } catch (error) {
550
- console.error('Failed to load merchants:', error);
551
- grid.innerHTML = '<div class="empty-state"><h3>Error loading merchants</h3><p>Please try again later.</p></div>';
552
- }
553
- }
554
-
555
- function renderMerchantCard(merchant) {
556
- const initials = (merchant.displayName || merchant.domain).substring(0, 2).toUpperCase();
557
- const transports = merchant.transports || [];
558
-
559
- return `
560
- <a href="https://${merchant.domain}" target="_blank" class="merchant-card">
561
- <div class="merchant-header">
562
- <div class="merchant-logo">
563
- ${merchant.logoUrl ? `<img src="${merchant.logoUrl}" alt="${merchant.displayName}">` : initials}
564
- </div>
565
- <div class="merchant-info">
566
- <div class="merchant-name">${merchant.displayName}</div>
567
- <div class="merchant-domain">${merchant.domain}</div>
568
- </div>
569
- ${merchant.ucpGrade ? `<div class="merchant-grade grade-${merchant.ucpGrade}">${merchant.ucpGrade}</div>` : ''}
570
- </div>
571
- ${merchant.description ? `<div class="merchant-desc">${merchant.description}</div>` : ''}
572
- <div class="merchant-tags">
573
- ${merchant.category ? `<span class="merchant-tag">${formatCategory(merchant.category)}</span>` : ''}
574
- ${merchant.countryCode ? `<span class="merchant-tag">${merchant.countryCode}</span>` : ''}
575
- ${transports.map(t => `<span class="merchant-tag transport-tag">${t}</span>`).join('')}
576
- </div>
577
- </a>
578
- `;
579
- }
580
-
581
- function formatCategory(cat) {
582
- if (!cat) return '';
583
- return cat.charAt(0).toUpperCase() + cat.slice(1).replace(/-/g, ' ');
584
- }
585
-
586
- function updatePagination(pagination) {
587
- const paginationEl = document.getElementById('pagination');
588
- const pageInfo = document.getElementById('page-info');
589
- const prevBtn = document.getElementById('prev-page');
590
- const nextBtn = document.getElementById('next-page');
591
-
592
- if (pagination.totalPages <= 1) {
593
- paginationEl.style.display = 'none';
594
- return;
595
- }
596
-
597
- paginationEl.style.display = 'flex';
598
- pageInfo.textContent = `Page ${pagination.page} of ${pagination.totalPages}`;
599
- prevBtn.disabled = pagination.page <= 1;
600
- nextBtn.disabled = pagination.page >= pagination.totalPages;
601
- }
602
-
603
- // Search handler
604
- let searchTimeout;
605
- document.getElementById('search-input').addEventListener('input', (e) => {
606
- clearTimeout(searchTimeout);
607
- searchTimeout = setTimeout(() => {
608
- currentFilters.search = e.target.value;
609
- currentPage = 1;
610
- loadMerchants();
611
- }, 300);
612
- });
613
-
614
- // Pagination handlers
615
- document.getElementById('prev-page').addEventListener('click', () => {
616
- if (currentPage > 1) {
617
- currentPage--;
618
- loadMerchants();
619
- }
620
- });
621
-
622
- document.getElementById('next-page').addEventListener('click', () => {
623
- if (currentPage < totalPages) {
624
- currentPage++;
625
- loadMerchants();
626
- }
627
- });
628
-
629
- // Modal handlers
630
- const modal = document.getElementById('submit-modal');
631
- document.getElementById('open-submit-modal').addEventListener('click', (e) => {
632
- e.preventDefault();
633
- modal.classList.add('active');
634
- });
635
-
636
- document.getElementById('close-modal').addEventListener('click', () => {
637
- modal.classList.remove('active');
638
- });
639
-
640
- modal.addEventListener('click', (e) => {
641
- if (e.target === modal) modal.classList.remove('active');
642
- });
643
-
644
- // Submit form handler
645
- document.getElementById('submit-form').addEventListener('submit', async (e) => {
646
- e.preventDefault();
647
-
648
- const formMessage = document.getElementById('form-message');
649
- const submitBtn = document.getElementById('submit-btn');
650
-
651
- formMessage.style.display = 'none';
652
- submitBtn.disabled = true;
653
- submitBtn.textContent = 'Submitting...';
654
-
655
- try {
656
- const res = await fetch(`${API_BASE}/directory`, {
657
- method: 'POST',
658
- headers: { 'Content-Type': 'application/json' },
659
- body: JSON.stringify({
660
- domain: document.getElementById('submit-domain').value,
661
- displayName: document.getElementById('submit-name').value || undefined,
662
- description: document.getElementById('submit-description').value || undefined,
663
- category: document.getElementById('submit-category').value || undefined,
664
- countryCode: document.getElementById('submit-country').value || undefined,
665
- }),
666
- });
667
-
668
- const data = await res.json();
669
-
670
- if (!res.ok) {
671
- throw new Error(data.details || data.error || 'Submission failed');
672
- }
673
-
674
- formMessage.className = 'form-message success';
675
- formMessage.textContent = 'Store submitted successfully! It will appear in the directory shortly.';
676
- formMessage.style.display = 'block';
677
-
678
- // Reset form and reload
679
- document.getElementById('submit-form').reset();
680
- setTimeout(() => {
681
- modal.classList.remove('active');
682
- loadMerchants();
683
- loadStats();
684
- }, 2000);
685
-
686
- } catch (error) {
687
- formMessage.className = 'form-message error';
688
- formMessage.textContent = error.message;
689
- formMessage.style.display = 'block';
690
- } finally {
691
- submitBtn.disabled = false;
692
- submitBtn.textContent = 'Submit Store';
693
- }
694
- });
695
-
696
- // Initialize
697
- loadStats();
698
- loadMerchants();
699
- </script>
700
- </body>
701
- </html>