aimeat 1.23.2 → 1.24.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 (239) hide show
  1. package/dist/locales/en.json +580 -21
  2. package/dist/locales/fi.json +592 -33
  3. package/dist/public/components/ContactCard.js +28 -0
  4. package/dist/public/css/views/admin.css +12 -0
  5. package/dist/public/css/views/agents-detail.css +62 -10
  6. package/dist/public/css/views/landing.css +87 -0
  7. package/dist/public/css/views/offers.css +96 -0
  8. package/dist/public/css/views/portfolio.css +18 -0
  9. package/dist/public/css/views/profile.css +153 -8
  10. package/dist/public/css/views/start.css +96 -0
  11. package/dist/public/css/views/workflows.css +111 -0
  12. package/dist/public/img/business-hero.png +0 -0
  13. package/dist/public/img/toimitus.png +0 -0
  14. package/dist/public/js/components/tag-cloud.js +46 -31
  15. package/dist/public/js/components/tag-editor.js +2 -2
  16. package/dist/public/js/recents.js +40 -0
  17. package/dist/public/js/services/admin.js +6 -0
  18. package/dist/public/js/services/agents.js +34 -0
  19. package/dist/public/js/services/offers.js +152 -0
  20. package/dist/public/js/services/packages.js +1 -0
  21. package/dist/public/js/services/workflows.js +57 -0
  22. package/dist/public/llms-template.txt +7 -0
  23. package/dist/public/spa.html +35 -3
  24. package/dist/public/views/admin/subdomains-tab.js +182 -0
  25. package/dist/public/views/admin.js +2 -0
  26. package/dist/public/views/business.js +119 -0
  27. package/dist/public/views/how-it-works.js +159 -0
  28. package/dist/public/views/landing.js +235 -0
  29. package/dist/public/views/portfolio.js +120 -23
  30. package/dist/public/views/pricing.js +75 -0
  31. package/dist/public/views/profile/access-tab.js +124 -51
  32. package/dist/public/views/profile/agents/agent-card.js +68 -37
  33. package/dist/public/views/profile/agents/shared-board.js +56 -22
  34. package/dist/public/views/profile/agents/tab-activity.js +24 -12
  35. package/dist/public/views/profile/agents/tab-agent-config.js +31 -1
  36. package/dist/public/views/profile/agents/tab-data-access.js +9 -13
  37. package/dist/public/views/profile/agents/tab-integration.js +27 -1
  38. package/dist/public/views/profile/agents/tab-messages.js +6 -11
  39. package/dist/public/views/profile/agents/tab-quality.js +45 -5
  40. package/dist/public/views/profile/agents-tab.js +41 -6
  41. package/dist/public/views/profile/apps-tab.js +7 -1
  42. package/dist/public/views/profile/knowledge-tab.js +4 -1
  43. package/dist/public/views/profile/landing-page.js +428 -123
  44. package/dist/public/views/profile/memory-tab.js +409 -211
  45. package/dist/public/views/profile/nodes-tab.js +18 -3
  46. package/dist/public/views/profile/offers-tab.js +310 -0
  47. package/dist/public/views/profile/organisms-tab.js +289 -28
  48. package/dist/public/views/profile/packages-tab.js +152 -16
  49. package/dist/public/views/profile/portfolio-tab.js +98 -31
  50. package/dist/public/views/profile/workflows-form.js +220 -0
  51. package/dist/public/views/profile/workflows-tab.js +244 -0
  52. package/dist/public/views/profile.js +4 -0
  53. package/dist/public/views/start-flows.js +281 -0
  54. package/dist/public/views/start.js +258 -0
  55. package/dist/src/cli/connect/mcp/tools/index.d.ts.map +1 -1
  56. package/dist/src/cli/connect/mcp/tools/index.js +2 -0
  57. package/dist/src/cli/connect/mcp/tools/index.js.map +1 -1
  58. package/dist/src/cli/connect/mcp/tools/workflows.d.ts +12 -0
  59. package/dist/src/cli/connect/mcp/tools/workflows.d.ts.map +1 -0
  60. package/dist/src/cli/connect/mcp/tools/workflows.js +37 -0
  61. package/dist/src/cli/connect/mcp/tools/workflows.js.map +1 -0
  62. package/dist/src/cli/connect/tool-call.d.ts.map +1 -1
  63. package/dist/src/cli/connect/tool-call.js +37 -0
  64. package/dist/src/cli/connect/tool-call.js.map +1 -1
  65. package/dist/src/generated/api-types.d.ts +1474 -66
  66. package/dist/src/generated/api-types.d.ts.map +1 -1
  67. package/dist/src/generated/prisma-postgres/edge.js +15 -3
  68. package/dist/src/generated/prisma-postgres/index-browser.js +12 -0
  69. package/dist/src/generated/prisma-postgres/index.d.ts +1476 -160
  70. package/dist/src/generated/prisma-postgres/index.js +15 -3
  71. package/dist/src/generated/prisma-postgres/package.json +1 -1
  72. package/dist/src/generated/prisma-postgres/schema.prisma +11 -0
  73. package/dist/src/generated/prisma-postgres/wasm.js +15 -3
  74. package/dist/src/mcp/annotations.d.ts.map +1 -1
  75. package/dist/src/mcp/annotations.js +3 -0
  76. package/dist/src/mcp/annotations.js.map +1 -1
  77. package/dist/src/mcp/catalog/definitions.d.ts.map +1 -1
  78. package/dist/src/mcp/catalog/definitions.js +27 -0
  79. package/dist/src/mcp/catalog/definitions.js.map +1 -1
  80. package/dist/src/mcp/catalog/scopes.d.ts.map +1 -1
  81. package/dist/src/mcp/catalog/scopes.js +5 -1
  82. package/dist/src/mcp/catalog/scopes.js.map +1 -1
  83. package/dist/src/mcp/catalog/surfaces.d.ts.map +1 -1
  84. package/dist/src/mcp/catalog/surfaces.js +1 -0
  85. package/dist/src/mcp/catalog/surfaces.js.map +1 -1
  86. package/dist/src/mcp/index.d.ts.map +1 -1
  87. package/dist/src/mcp/index.js +2 -0
  88. package/dist/src/mcp/index.js.map +1 -1
  89. package/dist/src/mcp/workflows.d.ts +18 -0
  90. package/dist/src/mcp/workflows.d.ts.map +1 -0
  91. package/dist/src/mcp/workflows.js +68 -0
  92. package/dist/src/mcp/workflows.js.map +1 -0
  93. package/dist/src/middleware/subdomain.d.ts +28 -0
  94. package/dist/src/middleware/subdomain.d.ts.map +1 -0
  95. package/dist/src/middleware/subdomain.js +32 -0
  96. package/dist/src/middleware/subdomain.js.map +1 -0
  97. package/dist/src/models/offer-schemas.d.ts +236 -0
  98. package/dist/src/models/offer-schemas.d.ts.map +1 -0
  99. package/dist/src/models/offer-schemas.js +112 -0
  100. package/dist/src/models/offer-schemas.js.map +1 -0
  101. package/dist/src/models/workflow-schemas.d.ts +220 -0
  102. package/dist/src/models/workflow-schemas.d.ts.map +1 -0
  103. package/dist/src/models/workflow-schemas.js +102 -0
  104. package/dist/src/models/workflow-schemas.js.map +1 -0
  105. package/dist/src/routes/agent-tasks.d.ts.map +1 -1
  106. package/dist/src/routes/agent-tasks.js +49 -0
  107. package/dist/src/routes/agent-tasks.js.map +1 -1
  108. package/dist/src/routes/agents.d.ts.map +1 -1
  109. package/dist/src/routes/agents.js +146 -1
  110. package/dist/src/routes/agents.js.map +1 -1
  111. package/dist/src/routes/apps-backup.d.ts +20 -0
  112. package/dist/src/routes/apps-backup.d.ts.map +1 -0
  113. package/dist/src/routes/apps-backup.js +165 -0
  114. package/dist/src/routes/apps-backup.js.map +1 -0
  115. package/dist/src/routes/ghii.d.ts.map +1 -1
  116. package/dist/src/routes/ghii.js +4 -0
  117. package/dist/src/routes/ghii.js.map +1 -1
  118. package/dist/src/routes/instances.d.ts +2 -1
  119. package/dist/src/routes/instances.d.ts.map +1 -1
  120. package/dist/src/routes/instances.js +59 -1
  121. package/dist/src/routes/instances.js.map +1 -1
  122. package/dist/src/routes/memory.d.ts.map +1 -1
  123. package/dist/src/routes/memory.js +5 -0
  124. package/dist/src/routes/memory.js.map +1 -1
  125. package/dist/src/routes/packages.d.ts.map +1 -1
  126. package/dist/src/routes/packages.js +32 -0
  127. package/dist/src/routes/packages.js.map +1 -1
  128. package/dist/src/routes/permissions.d.ts +12 -0
  129. package/dist/src/routes/permissions.d.ts.map +1 -1
  130. package/dist/src/routes/permissions.js +35 -3
  131. package/dist/src/routes/permissions.js.map +1 -1
  132. package/dist/src/routes/portal.d.ts +3 -0
  133. package/dist/src/routes/portal.d.ts.map +1 -1
  134. package/dist/src/routes/portal.js +12 -0
  135. package/dist/src/routes/portal.js.map +1 -1
  136. package/dist/src/routes/public-stats.d.ts +15 -0
  137. package/dist/src/routes/public-stats.d.ts.map +1 -0
  138. package/dist/src/routes/public-stats.js +90 -0
  139. package/dist/src/routes/public-stats.js.map +1 -0
  140. package/dist/src/routes/sse.d.ts +7 -2
  141. package/dist/src/routes/sse.d.ts.map +1 -1
  142. package/dist/src/routes/sse.js +35 -5
  143. package/dist/src/routes/sse.js.map +1 -1
  144. package/dist/src/routes/stats.d.ts.map +1 -1
  145. package/dist/src/routes/stats.js +13 -0
  146. package/dist/src/routes/stats.js.map +1 -1
  147. package/dist/src/routes/subdomains.d.ts +33 -0
  148. package/dist/src/routes/subdomains.d.ts.map +1 -0
  149. package/dist/src/routes/subdomains.js +208 -0
  150. package/dist/src/routes/subdomains.js.map +1 -0
  151. package/dist/src/routes/workflows.d.ts +30 -0
  152. package/dist/src/routes/workflows.d.ts.map +1 -0
  153. package/dist/src/routes/workflows.js +178 -0
  154. package/dist/src/routes/workflows.js.map +1 -0
  155. package/dist/src/server-bootstrap/routes-loader.d.ts +2 -0
  156. package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
  157. package/dist/src/server-bootstrap/routes-loader.js +19 -2
  158. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  159. package/dist/src/server-bootstrap/service-init.d.ts +2 -0
  160. package/dist/src/server-bootstrap/service-init.d.ts.map +1 -1
  161. package/dist/src/server-bootstrap/service-init.js +6 -0
  162. package/dist/src/server-bootstrap/service-init.js.map +1 -1
  163. package/dist/src/server.d.ts.map +1 -1
  164. package/dist/src/server.js +5 -0
  165. package/dist/src/server.js.map +1 -1
  166. package/dist/src/services/apps-backup-export.d.ts +22 -0
  167. package/dist/src/services/apps-backup-export.d.ts.map +1 -0
  168. package/dist/src/services/apps-backup-export.js +136 -0
  169. package/dist/src/services/apps-backup-export.js.map +1 -0
  170. package/dist/src/services/apps-backup-import.d.ts +120 -0
  171. package/dist/src/services/apps-backup-import.d.ts.map +1 -0
  172. package/dist/src/services/apps-backup-import.js +259 -0
  173. package/dist/src/services/apps-backup-import.js.map +1 -0
  174. package/dist/src/services/component-registrar.d.ts +17 -0
  175. package/dist/src/services/component-registrar.d.ts.map +1 -1
  176. package/dist/src/services/component-registrar.js +63 -5
  177. package/dist/src/services/component-registrar.js.map +1 -1
  178. package/dist/src/services/handbooks/agent.d.ts +1 -1
  179. package/dist/src/services/handbooks/agent.d.ts.map +1 -1
  180. package/dist/src/services/handbooks/agent.js +10 -0
  181. package/dist/src/services/handbooks/agent.js.map +1 -1
  182. package/dist/src/services/handbooks/appdev.d.ts +1 -1
  183. package/dist/src/services/handbooks/appdev.d.ts.map +1 -1
  184. package/dist/src/services/handbooks/appdev.js +5 -2
  185. package/dist/src/services/handbooks/appdev.js.map +1 -1
  186. package/dist/src/services/scheduler.d.ts +6 -0
  187. package/dist/src/services/scheduler.d.ts.map +1 -1
  188. package/dist/src/services/scheduler.js +22 -0
  189. package/dist/src/services/scheduler.js.map +1 -1
  190. package/dist/src/services/schema-validator.d.ts +9 -0
  191. package/dist/src/services/schema-validator.d.ts.map +1 -1
  192. package/dist/src/services/schema-validator.js +16 -0
  193. package/dist/src/services/schema-validator.js.map +1 -1
  194. package/dist/src/services/workflow/engine.d.ts +84 -0
  195. package/dist/src/services/workflow/engine.d.ts.map +1 -0
  196. package/dist/src/services/workflow/engine.js +545 -0
  197. package/dist/src/services/workflow/engine.js.map +1 -0
  198. package/dist/src/services/workflow/eval-context.d.ts +23 -0
  199. package/dist/src/services/workflow/eval-context.d.ts.map +1 -0
  200. package/dist/src/services/workflow/eval-context.js +52 -0
  201. package/dist/src/services/workflow/eval-context.js.map +1 -0
  202. package/dist/src/services/workflow/lifecycle.d.ts +38 -0
  203. package/dist/src/services/workflow/lifecycle.d.ts.map +1 -0
  204. package/dist/src/services/workflow/lifecycle.js +99 -0
  205. package/dist/src/services/workflow/lifecycle.js.map +1 -0
  206. package/dist/src/services/workflow/signal-eval.d.ts +70 -0
  207. package/dist/src/services/workflow/signal-eval.d.ts.map +1 -0
  208. package/dist/src/services/workflow/signal-eval.js +174 -0
  209. package/dist/src/services/workflow/signal-eval.js.map +1 -0
  210. package/dist/src/services/workflow/store.d.ts +89 -0
  211. package/dist/src/services/workflow/store.d.ts.map +1 -0
  212. package/dist/src/services/workflow/store.js +284 -0
  213. package/dist/src/services/workflow/store.js.map +1 -0
  214. package/dist/src/storage/interface.d.ts +13 -3
  215. package/dist/src/storage/interface.d.ts.map +1 -1
  216. package/dist/src/storage/interface.js.map +1 -1
  217. package/dist/src/storage/providers/mongodb/index.d.ts +10 -1
  218. package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
  219. package/dist/src/storage/providers/mongodb/index.js +76 -0
  220. package/dist/src/storage/providers/mongodb/index.js.map +1 -1
  221. package/dist/src/storage/providers/sqlite/index.d.ts +8 -1
  222. package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
  223. package/dist/src/storage/providers/sqlite/index.js +61 -0
  224. package/dist/src/storage/providers/sqlite/index.js.map +1 -1
  225. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  226. package/dist/src/storage/providers/sqlite/schema.js +11 -0
  227. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  228. package/dist/src/storage/repositories/index.d.ts +1 -0
  229. package/dist/src/storage/repositories/index.d.ts.map +1 -1
  230. package/dist/src/storage/repositories/package.repository.d.ts +2 -0
  231. package/dist/src/storage/repositories/package.repository.d.ts.map +1 -1
  232. package/dist/src/storage/repositories/subdomain-site.repository.d.ts +19 -0
  233. package/dist/src/storage/repositories/subdomain-site.repository.d.ts.map +1 -0
  234. package/dist/src/storage/repositories/subdomain-site.repository.js +2 -0
  235. package/dist/src/storage/repositories/subdomain-site.repository.js.map +1 -0
  236. package/dist/static/app-catalog.html +1139 -28
  237. package/package.json +1 -1
  238. package/prisma/schema.postgres.prisma +11 -0
  239. package/prisma/schema.prisma +11 -0
@@ -67,9 +67,11 @@
67
67
  <!-- View CSS preloaded so they are ready before any view renders (prevents FOUC) -->
68
68
  <!-- hrefs are stamped with ?v=BUILD_ID at serve time by portal.ts serveSpa() -->
69
69
  <link rel="stylesheet" href="/css/views/portal.css">
70
+ <link rel="stylesheet" href="/css/views/landing.css">
70
71
  <link rel="stylesheet" href="/css/views/portal-classic.css">
71
72
  <link rel="stylesheet" href="/css/views/portal-dev.css">
72
73
  <link rel="stylesheet" href="/css/views/profile.css">
74
+ <link rel="stylesheet" href="/css/views/offers.css">
73
75
  <link rel="stylesheet" href="/css/views/foundry.css">
74
76
  <link rel="stylesheet" href="/css/views/calibrator.css">
75
77
  <link rel="stylesheet" href="/css/views/hobbies.css">
@@ -82,10 +84,12 @@
82
84
  <link rel="stylesheet" href="/css/views/capabilities.css">
83
85
  <link rel="stylesheet" href="/css/views/agents-detail.css">
84
86
  <link rel="stylesheet" href="/css/views/scheduler.css">
87
+ <link rel="stylesheet" href="/css/views/workflows.css">
85
88
  <link rel="stylesheet" href="/css/views/admin.css">
86
89
  <link rel="stylesheet" href="/css/views/admin-agent-integration.css">
87
90
  <link rel="stylesheet" href="/css/views/public-knowledge-viewer.css">
88
91
  <link rel="stylesheet" href="/css/views/public-workspace-viewer.css">
92
+ <link rel="stylesheet" href="/css/views/start.css">
89
93
  <script type="importmap">
90
94
  {
91
95
  "imports": {
@@ -98,6 +102,7 @@
98
102
  "/js/format.js": "/js/format.js",
99
103
  "/js/api.js": "/js/api.js",
100
104
  "/js/hooks.js": "/js/hooks.js",
105
+ "/js/recents.js": "/js/recents.js",
101
106
  "/js/services/admin.js": "/js/services/admin.js",
102
107
  "/js/services/agents.js": "/js/services/agents.js",
103
108
  "/js/services/apps.js": "/js/services/apps.js",
@@ -151,6 +156,7 @@
151
156
  "/js/services/memory.js": "/js/services/memory.js",
152
157
  "/js/services/nodes.js": "/js/services/nodes.js",
153
158
  "/js/services/organisms.js": "/js/services/organisms.js",
159
+ "/js/services/offers.js": "/js/services/offers.js",
154
160
  "/js/services/packages.js": "/js/services/packages.js",
155
161
  "/js/services/security.js": "/js/services/security.js",
156
162
  "/js/services/stats.js": "/js/services/stats.js",
@@ -165,6 +171,7 @@
165
171
  "/components/Mermaid.js": "/components/Mermaid.js",
166
172
  "/components/NotificationBell.js": "/components/NotificationBell.js",
167
173
  "/components/CopyButton.js": "/components/CopyButton.js",
174
+ "/components/ContactCard.js": "/components/ContactCard.js",
168
175
  "/components/Spinner.js": "/components/Spinner.js",
169
176
  "/components/EmptyState.js": "/components/EmptyState.js",
170
177
  "/components/ToggleSwitch.js": "/components/ToggleSwitch.js",
@@ -193,7 +200,8 @@
193
200
  "/js/services/agent-messages.js": "/js/services/agent-messages.js",
194
201
  "/js/services/agent-integration.js": "/js/services/agent-integration.js",
195
202
  "/js/services/admin-agent-integration.js": "/js/services/admin-agent-integration.js",
196
- "/js/services/schedules.js": "/js/services/schedules.js"
203
+ "/js/services/schedules.js": "/js/services/schedules.js",
204
+ "/js/services/workflows.js": "/js/services/workflows.js"
197
205
  }
198
206
  }
199
207
  </script>
@@ -255,7 +263,14 @@
255
263
  // insufficient — modules stay in the module registry for the full session).
256
264
  const B = window.__B || '';
257
265
  const ROUTES = {
258
- '/v1/portal': () => import('/views/portal.js' + B),
266
+ // Landing/portal split (2026-06-10): the route serves the logged-out landing;
267
+ // logged-in visitors are forwarded to /v1/profile by the view itself. The old
268
+ // portal view stays reachable at /v1/classic (prompt boxes / "Build").
269
+ '/v1/portal': () => import('/views/landing.js' + B),
270
+ '/v1/pricing': () => import('/views/pricing.js' + B),
271
+ '/v1/start': () => import('/views/start.js' + B),
272
+ '/v1/how-it-works': () => import('/views/how-it-works.js' + B),
273
+ '/v1/business': () => import('/views/business.js' + B),
259
274
  '/v1/profile': () => import('/views/profile.js' + B),
260
275
  '/v1/guides': () => import('/views/guides.js' + B),
261
276
  '/v1/hobbies': () => import('/views/hobbies.js' + B),
@@ -347,6 +362,14 @@
347
362
  // Only intercept internal /v1/ links
348
363
  if (!href || !href.startsWith('/v1/')) return;
349
364
  if (e.ctrlKey || e.metaKey || e.shiftKey) return;
365
+ // Respect explicit new-tab / new-window targets — without this, an
366
+ // <a href="/v1/apps/.../launch?mode=inline" target="_blank"> from a
367
+ // child component would be hijacked, navigated in the same tab via
368
+ // matchRoute (which doesn't know /v1/apps/...), and dump the user
369
+ // back on the landing page.
370
+ const target = a.getAttribute('target');
371
+ if (target && target !== '_self' && target !== '') return;
372
+ if (a.hasAttribute('download')) return;
350
373
  e.preventDefault();
351
374
  navigate(href);
352
375
  };
@@ -438,6 +461,10 @@
438
461
 
439
462
  const handleNav = (e, path) => {
440
463
  e.preventDefault();
464
+ // Mark in-app navigation: the landing redirects logged-in users to their Home
465
+ // ONLY on direct arrival (bookmark/external link), never when they deliberately
466
+ // navigated here from inside the app (e.g. the brand link).
467
+ try { sessionStorage.setItem('aimeat.in-app', '1'); } catch {}
441
468
  navigate(path);
442
469
  };
443
470
 
@@ -456,7 +483,12 @@
456
483
  <button class="topnav-burger" aria-label="Menu" aria-expanded=${menuOpen ? 'true' : 'false'}
457
484
  onClick=${() => setMenuOpen(o => !o)}>☰</button>
458
485
  <div class="topnav-menu ${menuOpen ? 'open' : ''}">
459
- <a href="/v1/classic" onClick=${(e) => { handleNav(e, '/v1/classic'); setMenuOpen(false); }} class="${currentPath === '/v1/classic' ? 'active' : ''}">${t('nav.try')}</a>
486
+ <!-- "Try it" leads to the landing's path cards + app gallery — an empty
487
+ Welcome Board on /v1/classic was the weakest possible first experience.
488
+ Classic stays reachable deeper (landing card B "Start building"). -->
489
+ <a href="/v1/portal" onClick=${(e) => { handleNav(e, '/v1/portal'); setMenuOpen(false); }} class="${currentPath === '/v1/portal' ? 'active' : ''}">${t('nav.try')}</a>
490
+ <a href="/v1/how-it-works" onClick=${(e) => { handleNav(e, '/v1/how-it-works'); setMenuOpen(false); }} class="${currentPath === '/v1/how-it-works' ? 'active' : ''}">${t('nav.howItWorks') || 'How it works'}</a>
491
+ <a href="/v1/business" onClick=${(e) => { handleNav(e, '/v1/business'); setMenuOpen(false); }} class="${currentPath === '/v1/business' ? 'active' : ''}">${t('nav.business') || 'For your business'}</a>
460
492
  <a href="/v1/portal?view=dev" onClick=${(e) => { handleNav(e, '/v1/portal?view=dev'); setMenuOpen(false); }}>${t('nav.devView')}</a>
461
493
  <a href="/v1/help" onClick=${(e) => { handleNav(e, '/v1/help'); setMenuOpen(false); }} class="${currentPath === '/v1/help' ? 'active' : ''}">${t('nav.help')}</a>
462
494
  ${session && html`
@@ -0,0 +1,182 @@
1
+ /**
2
+ * @file subdomains-tab.js
3
+ * @description Admin dashboard Subdomains tab — operator management of
4
+ * subdomain → site mappings (serve a published app at `<sub>.<apex>` or
5
+ * 301-redirect). Table with enabled-toggle, create form, and
6
+ * type-to-confirm deletion.
7
+ * @structure SubdomainsAdminTab (default)
8
+ * @usage Mounted by the admin dashboard tab router (views/admin.js).
9
+ * @version-history
10
+ * v1.0.0 — 2026-06-12 — Initial: subdomain routing (operator-only management)
11
+ */
12
+ import { h } from 'preact';
13
+ import { useState, useEffect, useCallback } from 'preact/hooks';
14
+ import htm from 'htm';
15
+ const html = htm.bind(h);
16
+ import { t } from '/js/i18n.js';
17
+ import { dt, Spinner, Empty, ErrorBox } from './shared.js';
18
+ import { Modal } from '/components/Modal.js';
19
+ import * as adminService from '/js/services/admin.js';
20
+
21
+ export default function SubdomainsAdminTab() {
22
+ const [sites, setSites] = useState([]);
23
+ const [loading, setLoading] = useState(true);
24
+ const [error, setError] = useState(null);
25
+ const [showCreate, setShowCreate] = useState(false);
26
+ const [form, setForm] = useState({ subdomain: '', kind: 'app', target: '' });
27
+ const [formError, setFormError] = useState(null);
28
+ const [saving, setSaving] = useState(false);
29
+ // Type-to-confirm delete state: { subdomain, typed }
30
+ const [deleting, setDeleting] = useState(null);
31
+
32
+ const load = useCallback(async () => {
33
+ setLoading(true);
34
+ setError(null);
35
+ try {
36
+ const resp = await adminService.getSubdomainSites();
37
+ setSites(resp?.data?.sites || []);
38
+ } catch (err) {
39
+ setError(err?.message || String(err));
40
+ } finally {
41
+ setLoading(false);
42
+ }
43
+ }, []);
44
+
45
+ useEffect(() => { load(); }, [load]);
46
+
47
+ useEffect(() => {
48
+ const handler = () => { load(); };
49
+ window.addEventListener('aimeat-live-update', handler);
50
+ return () => window.removeEventListener('aimeat-live-update', handler);
51
+ }, [load]);
52
+
53
+ const create = useCallback(async () => {
54
+ setFormError(null);
55
+ setSaving(true);
56
+ try {
57
+ await adminService.createSubdomainSite({
58
+ subdomain: form.subdomain.trim().toLowerCase(),
59
+ kind: form.kind,
60
+ target: form.target.trim(),
61
+ });
62
+ setShowCreate(false);
63
+ setForm({ subdomain: '', kind: 'app', target: '' });
64
+ load();
65
+ } catch (err) {
66
+ setFormError(err?.message || String(err));
67
+ } finally {
68
+ setSaving(false);
69
+ }
70
+ }, [form, load]);
71
+
72
+ const toggleEnabled = useCallback(async (site) => {
73
+ try {
74
+ await adminService.updateSubdomainSite(site.subdomain, { enabled: !site.enabled });
75
+ load();
76
+ } catch (err) {
77
+ setError(err?.message || String(err));
78
+ }
79
+ }, [load]);
80
+
81
+ const doDelete = useCallback(async () => {
82
+ if (!deleting || deleting.typed !== deleting.subdomain) return;
83
+ try {
84
+ await adminService.deleteSubdomainSite(deleting.subdomain);
85
+ setDeleting(null);
86
+ load();
87
+ } catch (err) {
88
+ setError(err?.message || String(err));
89
+ setDeleting(null);
90
+ }
91
+ }, [deleting, load]);
92
+
93
+ // Public URL for a mapping, derived from the page's own host
94
+ const siteUrl = (sub) => `${location.protocol}//${sub}.${location.host}/`;
95
+
96
+ return html`
97
+ <p class="adm-text-sm adm-text-dim adm-mb-md">${t('admin.subdomains.desc')}</p>
98
+
99
+ <div class="adm-card">
100
+ <div class="adm-flex-between adm-mb-md">
101
+ <h2>${t('admin.subdomains.title')}</h2>
102
+ <button class="adm-btn-sm" onClick=${() => { setShowCreate(!showCreate); setFormError(null); }}>
103
+ ${showCreate ? t('common.cancel') : '+ ' + t('admin.subdomains.add')}
104
+ </button>
105
+ </div>
106
+
107
+ ${showCreate && html`
108
+ <div class="adm-subdomain-form adm-mb-md">
109
+ <input class="adm-input" placeholder=${t('admin.subdomains.subdomain')}
110
+ value=${form.subdomain}
111
+ onInput=${e => setForm({ ...form, subdomain: e.target.value })} />
112
+ <select class="adm-input" value=${form.kind}
113
+ onChange=${e => setForm({ ...form, kind: e.target.value })}>
114
+ <option value="app">${t('admin.subdomains.kindApp')}</option>
115
+ <option value="redirect">${t('admin.subdomains.kindRedirect')}</option>
116
+ </select>
117
+ <input class="adm-input" value=${form.target}
118
+ placeholder=${form.kind === 'app' ? t('admin.subdomains.targetHintApp') : t('admin.subdomains.targetHintRedirect')}
119
+ onInput=${e => setForm({ ...form, target: e.target.value })} />
120
+ <button class="adm-btn" disabled=${saving || !form.subdomain.trim() || !form.target.trim()}
121
+ onClick=${create}>${t('admin.subdomains.create')}</button>
122
+ ${formError && html`<div class="adm-text-error adm-text-sm">${formError}</div>`}
123
+ </div>
124
+ `}
125
+
126
+ ${error && html`<${ErrorBox} message=${error} />`}
127
+ ${loading ? html`<${Spinner} />` : sites.length === 0
128
+ ? html`<${Empty} text=${t('admin.subdomains.empty')} />`
129
+ : html`
130
+ <div class="scrollable">
131
+ <table>
132
+ <thead><tr>
133
+ <th>${t('admin.subdomains.subdomain')}</th>
134
+ <th>${t('admin.subdomains.kind')}</th>
135
+ <th>${t('admin.subdomains.target')}</th>
136
+ <th>${t('admin.subdomains.enabled')}</th>
137
+ <th>${t('admin.subdomains.createdBy')}</th>
138
+ <th>${t('admin.subdomains.created')}</th>
139
+ <th></th>
140
+ </tr></thead>
141
+ <tbody>
142
+ ${sites.map(s => html`
143
+ <tr>
144
+ <td class="mono">
145
+ <a href=${siteUrl(s.subdomain)} target="_blank" rel="noopener">\u{1F310} ${s.subdomain}</a>
146
+ </td>
147
+ <td>${s.kind === 'app' ? t('admin.subdomains.kindApp') : t('admin.subdomains.kindRedirect')}</td>
148
+ <td class="mono">${s.target}</td>
149
+ <td>
150
+ <input type="checkbox" checked=${s.enabled} onChange=${() => toggleEnabled(s)}
151
+ title=${t('admin.subdomains.enabled')} />
152
+ </td>
153
+ <td class="mono">${s.createdBy}</td>
154
+ <td>${dt(s.createdAt)}</td>
155
+ <td>
156
+ <button class="adm-btn-sm adm-btn-danger"
157
+ onClick=${() => setDeleting({ subdomain: s.subdomain, typed: '' })}>
158
+ ${t('admin.subdomains.delete')}
159
+ </button>
160
+ </td>
161
+ </tr>
162
+ `)}
163
+ </tbody>
164
+ </table>
165
+ </div>
166
+ `}
167
+ </div>
168
+
169
+ <${Modal} open=${!!deleting} onClose=${() => setDeleting(null)} title=${t('admin.subdomains.deleteTitle')}>
170
+ ${deleting && html`
171
+ <p>${t('admin.subdomains.deleteConfirm')} <strong class="mono">${deleting.subdomain}</strong></p>
172
+ <input class="adm-input adm-input-full" value=${deleting.typed}
173
+ onInput=${e => setDeleting({ ...deleting, typed: e.target.value })} />
174
+ <div class="adm-flex adm-mt-md adm-subdomain-modal-actions">
175
+ <button class="btn-ghost" onClick=${() => setDeleting(null)}>${t('common.cancel')}</button>
176
+ <button class="btn-danger-solid" disabled=${deleting.typed !== deleting.subdomain}
177
+ onClick=${doDelete}>${t('admin.subdomains.delete')}</button>
178
+ </div>
179
+ `}
180
+ <//>
181
+ `;
182
+ }
@@ -52,6 +52,7 @@ import CapabilitiesAdminTab from './admin/capabilities-tab.js';
52
52
  import AgentTasksAdminTab from './admin/agent-tasks-tab.js';
53
53
  import SharingGroupsAdminTab from './admin/sharing-groups-tab.js';
54
54
  import AgentIntegrationAdminTab from './admin/agent-integration-tab.js';
55
+ import SubdomainsAdminTab from './admin/subdomains-tab.js';
55
56
 
56
57
  // ── Sidebar nav structure ──
57
58
  const NAV_GROUPS = [
@@ -64,6 +65,7 @@ const NAV_GROUPS = [
64
65
  { id: 'maintenance', icon: '\u{1F6A7}', key: 'dashboard.maintenance',component: MaintenanceTab },
65
66
  { id: 'hooks', icon: '\u{1F517}', key: 'dashboard.hooks', component: HooksTab },
66
67
  { id: 'portal', icon: '\u{1F310}', key: 'dashboard.portal', component: PortalTab },
68
+ { id: 'subdomains', icon: '\u{1F517}', key: 'admin.subdomains.title', component: SubdomainsAdminTab },
67
69
  { id: 'stats', icon: '\u{1F4C8}', key: 'dashboard.stats', component: StatsTab },
68
70
  { id: 'prompts', icon: '\u{1F4DD}', key: 'dashboard.promptsTab', component: PromptsTab },
69
71
  ]},
@@ -0,0 +1,119 @@
1
+ /**
2
+ * @file business.js
3
+ * @description "For your business" page: night-shift hero, six outcome cases
4
+ * (first-person headline + live proof + CTA), an all-of-this-is-production
5
+ * footer line and the shared ContactCard. Evidence links resolve to LIVE data
6
+ * (public apps / the FreePartyLights public status page) — never the Wellprepd
7
+ * organism (sensitive). No protocol terms; "organism" and "node" are product
8
+ * names, explained on first use.
9
+ * @usage routed at /v1/business by spa.html
10
+ * @version-history
11
+ * v1.0.0 — 2026-06-10 — Initial (owner spec).
12
+ */
13
+ import { h } from 'preact';
14
+ import { useState, useEffect } from 'preact/hooks';
15
+ import htm from 'htm';
16
+ const html = htm.bind(h);
17
+ import { t } from '/js/i18n.js';
18
+ import { escHtml } from '/js/utils.js';
19
+ import { ContactCard } from '/components/ContactCard.js';
20
+
21
+ const tr = (key, fallback) => { const v = t(key); return v && v !== key ? v : fallback; };
22
+ const CONTACT = 'mailto:jouni.miikki@aimeat.io';
23
+
24
+ export default function Business({ navigate }) {
25
+ // Live evidence: tonight's paper (public app) + the FreePartyLights public status
26
+ // page (resolved by name so the link works wherever the organism lives — and is
27
+ // simply hidden on nodes that don't have it). NEVER link the Wellprepd organism.
28
+ const [paper, setPaper] = useState(null);
29
+ useEffect(() => {
30
+ fetch('/v1/apps?limit=100').then(r => r.json()).then(j => {
31
+ const apps = j?.data?.apps || [];
32
+ setPaper(apps.find(a => /sanomat/i.test(a.filename || '') || /sanomat/i.test(a.name || '')) || null);
33
+ }).catch(() => {});
34
+ }, []);
35
+ // The FreePartyLights public status page (org + workspace ids on aimeat.io; public
36
+ // document sharing verified ON — 5 docs readable without auth). NEVER Wellprepd.
37
+ const fplHref = 'https://aimeat.io/v1/publicworkspaceviewer?org=27b89c13-1cc7-4fde-9c3f-91c4c7d06aa9&ws=ws-mq4y3131522';
38
+
39
+ const Case = ({ id, title, text, quote, quoteBy, proofHref, proofLabel, proofNote, ctaLabel, ctaHref }) => html`
40
+ <div class="ld-case" id=${id}>
41
+ <h3 class="ld-case-title">${title}</h3>
42
+ <p class="ld-case-text">${text}</p>
43
+ ${quote && html`<blockquote class="ld-case-quote">“${quote}”<footer>— ${quoteBy}</footer></blockquote>`}
44
+ ${proofHref
45
+ ? html`<a class="ld-case-proof" href=${proofHref} target="_blank" rel="noopener">${proofLabel}</a>`
46
+ : proofNote && html`<div class="ld-case-proofnote">${proofNote}</div>`}
47
+ <a class="ld-path-cta" href=${ctaHref}>${ctaLabel}</a>
48
+ </div>
49
+ `;
50
+
51
+ return html`
52
+ <div class="ld ld-business">
53
+ <!-- Hero: the night-shift image (assets/yovuoro_freepartylights.png) — the text
54
+ column sits in the image's empty right side as HTML, never baked in. -->
55
+ <section class="ld-bhero">
56
+ <img class="ld-bhero-img" src="/img/business-hero.png" alt=""
57
+ onError=${(e) => { e.target.style.display = 'none'; e.target.closest('.ld-bhero')?.classList.add('ld-bhero--noimg'); }} />
58
+ <div class="ld-bhero-text">
59
+ <h1 class="ld-h1">${tr('biz.heroTitle', 'A digital employee that never sleeps')}</h1>
60
+ <p class="ld-hero-sub">${tr('biz.heroSub', 'Scheduled agents fetch, write, report and ship while you do something else. Every step traceable.')}</p>
61
+ </div>
62
+ </section>
63
+
64
+ <div class="ld-cases">
65
+ <${Case} id="case-news"
66
+ title=${tr('biz.c1Title', 'I wake up already knowing what happened in my field')}
67
+ text=${tr('biz.c1Text', 'AIMEAT Sanomat writes itself every evening. Six agents, zero human hours.')}
68
+ proofHref=${paper
69
+ ? `/v1/apps/${encodeURIComponent(paper.owner)}/${encodeURIComponent(paper.filename)}?mode=inline`
70
+ : 'https://aimeat.io/v1/apps/happydude500001/laimeat-sanomat.html?mode=inline'}
71
+ proofLabel=${tr('biz.c1Proof', "Read tonight's paper →") + (paper?.created_at && paper.created_at.slice(0, 10) === new Date().toISOString().slice(0, 10) ? ` · ${tr('biz.writtenToday', 'written today at')} ${new Date(paper.created_at).toLocaleTimeString('fi-FI', { hour: '2-digit', minute: '2-digit' })}` : '')}
72
+ ctaLabel=${tr('biz.c1Cta', 'The same, from your topics →')} ctaHref=${CONTACT} />
73
+ <${Case} id="case-report"
74
+ title=${tr('biz.c2Title', 'My 7 AM report is waiting on my phone')}
75
+ text=${tr('biz.c2Text', 'Tell it once what you want to know every morning. An agent gathers, summarizes and sends.')}
76
+ proofNote=${tr('biz.c2Proof', 'Example (anonymized, sent today 07:00): "3 new bookings · 1 review (4★, answered) · competitor X dropped prices · suggested action: weekend campaign."')}
77
+ ctaLabel=${tr('biz.c2Cta', 'Order your morning report →')} ctaHref=${CONTACT} />
78
+ <${Case} id="case-radar"
79
+ title=${tr('biz.c3Title', "I know what's being said about us — and what to do about it")}
80
+ text=${tr('biz.c3Text', 'An agent tracks mentions, competitors and trends, and produces an action list — not just a report.')}
81
+ proofNote=${tr('biz.c3Proof', 'Example action list: "1) Answer the review on platform Y today. 2) Trend Z is rising — draft a post. 3) Competitor launched a campaign — counter-offer attached."')}
82
+ ctaLabel=${tr('biz.c3Cta', 'Start the radar →')} ctaHref=${CONTACT} />
83
+ <${Case} id="case-dev"
84
+ title=${tr('biz.c4Title', 'My project knows its own status')}
85
+ text=${tr('biz.c4Text', 'When developers have agents, sprints slow things down. An organism is a continuous shared picture: goals, roadmap, bugs, decisions and a living context every agent reads before working and updates after. A human approves the decisions; everything else moves at agent speed.')}
86
+ quote=${tr('biz.c4Quote', 'This could replace Jira.')} quoteBy=${tr('biz.c4QuoteBy', 'game developer, large studio')}
87
+ proofHref=${fplHref}
88
+ proofLabel=${tr('biz.c4Proof', 'Open the FreePartyLights status page →')}
89
+ proofNote=${tr('biz.proofUnavailable', 'Live proof runs on aimeat.io.')}
90
+ ctaLabel=${tr('biz.c4Cta', 'Turn your project into an organism →')} ctaHref=${CONTACT} />
91
+ <${Case} id="case-feedback"
92
+ title=${tr('biz.c5Title', "A user's idea shipped the same day")}
93
+ text=${tr('biz.c5Text', 'A user left an idea in the morning. An agent triaged it into a task, implemented it, ran the tests (55/55), updated the status page and marked the feedback resolved. A human confirmed it works. The whole chain is traceable: feedback → task → release.')}
94
+ proofHref=${fplHref}
95
+ proofLabel=${tr('biz.c5Proof', 'Same status page — see zenmasta001 → task-015 →')}
96
+ proofNote=${tr('biz.proofUnavailable', 'Live proof runs on aimeat.io.')}
97
+ ctaLabel=${tr('biz.c5Cta', 'Open a feedback loop for your customers →')} ctaHref=${CONTACT} />
98
+ <${Case} id="case-node"
99
+ title=${tr('biz.c6Title', 'Kalle runs his whole company on its own node')}
100
+ text=${tr('biz.c6Text', "The Original Miskate Oy's homepage lives in agent memory on its own AIMEAT node, federated into the network. The vartija crew audits the node every morning at 04:00; the tutkijatar crew runs research overnight.")}
101
+ quote=${'Every discovery is a mistake that survived long enough to prove itself.'}
102
+ quoteBy=${'Kalle Määttä, founder, The Original Miskate Oy'}
103
+ proofHref="https://originalmiskate.com"
104
+ proofLabel=${tr('biz.c6Proof', 'originalmiskate.com — a living node →')}
105
+ ctaLabel=${tr('biz.c6Cta', 'See hosting packages →')} ctaHref="/v1/pricing" />
106
+ </div>
107
+
108
+ <div class="ld-stats">
109
+ <div class="ld-stats-line">${tr('biz.allLive', 'Everything above runs in production right now. No proof is a mockup.')}</div>
110
+ <div class="ld-ctarow">
111
+ <a class="btn-primary" href=${CONTACT}>${tr('biz.ctaDemo', 'Book a demo →')}</a>
112
+ <a class="btn-outline" href="/v1/pricing" onClick=${(e) => { e.preventDefault(); navigate('/v1/pricing'); }}>${tr('biz.ctaPricing', 'See pricing →')}</a>
113
+ </div>
114
+ </div>
115
+
116
+ <${ContactCard} />
117
+ </div>
118
+ `;
119
+ }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * @file how-it-works.js
3
+ * @description "How it works" page: H1 + intro, a hand-written inline SVG pipeline
4
+ * diagram (models → agents → organism → your assets, human at the gate), three
5
+ * explainer cards, a live example box (real public app link) and a CTA row.
6
+ * Diagram texts are i18n keys so the FI/EN switch translates the SVG too; a
7
+ * vertical variant renders on mobile. No protocol terms in body copy.
8
+ * @usage routed at /v1/how-it-works by spa.html
9
+ * @version-history
10
+ * v1.0.0 — 2026-06-10 — Initial (owner spec).
11
+ */
12
+ import { h } from 'preact';
13
+ import { useState, useEffect } from 'preact/hooks';
14
+ import htm from 'htm';
15
+ const html = htm.bind(h);
16
+ import { t } from '/js/i18n.js';
17
+ import { escHtml } from '/js/utils.js';
18
+
19
+ const tr = (key, fallback) => { const v = t(key); return v && v !== key ? v : fallback; };
20
+
21
+ /* Diagram block: rect + up to 5 short text lines. Colors via CSS vars so dark mode works.
22
+ * NB: htm passes attribute values as STRINGS — coerce before arithmetic, or "10" + 95
23
+ * becomes "1095" and every text lands far outside its box (the empty-boxes bug). */
24
+ function Block({ x, y, w, h: hh, title, lines, hot }) {
25
+ const X = Number(x), Y = Number(y), W = Number(w);
26
+ return html`
27
+ <g>
28
+ <rect x=${X} y=${Y} width=${W} height=${Number(hh)} rx="10"
29
+ fill="var(--card)" stroke=${hot ? 'var(--accent, #E8564A)' : 'var(--border)'} stroke-width=${hot ? 2 : 1.5} />
30
+ <text x=${X + W / 2} y=${Y + 22} text-anchor="middle" font-size="13" font-weight="700" fill="var(--text)">${title}</text>
31
+ ${lines.map((l, i) => html`
32
+ <text key=${i} x=${X + W / 2} y=${Y + 42 + i * 16} text-anchor="middle" font-size="11" fill="var(--text-dim)">${l}</text>
33
+ `)}
34
+ </g>
35
+ `;
36
+ }
37
+
38
+ /* The model-swap caption, wrapped to two centered lines so it stays inside the viewBox. */
39
+ function Caption({ x, y, note }) {
40
+ const parts = note.split(/\s*[—,]\s*/);
41
+ const lines = parts.length >= 2 ? [parts[0], parts.slice(1).join(', ')] : [note];
42
+ return html`
43
+ <g>
44
+ ${lines.map((l, i) => html`
45
+ <text key=${i} x=${Number(x)} y=${Number(y) + i * 13} text-anchor="middle" font-size="10" font-style="italic" fill="var(--text-dim)">${l}</text>
46
+ `)}
47
+ </g>
48
+ `;
49
+ }
50
+
51
+ // Same string-coercion trap as Block: htm passes attrs as strings, and "95" + 4
52
+ // is "954" — the arrowhead's third point leaked far below the line. Coerce first.
53
+ const Arrow = (props) => {
54
+ const x1 = Number(props.x1), y1 = Number(props.y1), x2 = Number(props.x2), y2 = Number(props.y2);
55
+ return html`
56
+ <g stroke="var(--text-dim)" stroke-width="1.5" fill="var(--text-dim)">
57
+ <line x1=${x1} y1=${y1} x2=${x2} y2=${y2} />
58
+ <polygon points=${`${x2},${y2} ${x2 - 7},${y2 - 4} ${x2 - 7},${y2 + 4}`}
59
+ transform=${y1 === y2 ? '' : `rotate(90 ${x2} ${y2})`} />
60
+ </g>
61
+ `;
62
+ };
63
+
64
+ function Diagram() {
65
+ const models = { title: tr('hiw.dModels', 'AI models'), lines: [tr('hiw.dModels1', 'Swappable:'), tr('hiw.dModels2', 'Claude, GPT, Grok'), tr('hiw.dModels3', 'CrewAI, your own')] };
66
+ const agents = { title: tr('hiw.dAgents', 'Agents'), lines: [tr('hiw.dAgents1', 'A job for an AI:'), tr('hiw.dAgents2', 'identity, memory'), tr('hiw.dAgents3', 'task queue, schedule'), tr('hiw.dAgents4', 'budget, quality')] };
67
+ const org = { title: tr('hiw.dOrg', 'Organism'), lines: [tr('hiw.dOrg1', 'Shared truth:'), tr('hiw.dOrg2', 'workspaces, versions'), tr('hiw.dOrg3', 'humans + agents'), tr('hiw.dOrg4', 'publish gate')] };
68
+ const assets = { title: tr('hiw.dAssets', 'Your assets'), lines: [tr('hiw.dAssets1', 'Versioned knowledge'), tr('hiw.dAssets2', 'apps and reports')] };
69
+ const human = { title: tr('hiw.dHuman', 'Human at the gate'), lines: [tr('hiw.dHuman1', 'Decisions approved')] };
70
+ const note = tr('hiw.dNote', 'The model can change — work and memory stay');
71
+
72
+ return html`
73
+ <div class="hiw-diagram">
74
+ <!-- Desktop: left → right, assets below organism, human wired to the gate. -->
75
+ <svg class="hiw-svg hiw-svg--wide" viewBox="0 0 880 330" role="img" aria-label=${tr('hiw.dAria', 'How AIMEAT works')}>
76
+ <${Block} x="10" y="40" w="190" h="110" title=${models.title} lines=${models.lines} />
77
+ <${Caption} x="105" y="172" note=${note} />
78
+ <${Arrow} x1="200" y1="95" x2="240" y2="95" />
79
+ <${Block} x="240" y="30" w="200" h="130" title=${agents.title} lines=${agents.lines} hot />
80
+ <${Arrow} x1="440" y1="95" x2="480" y2="95" />
81
+ <${Block} x="480" y="30" w="200" h="130" title=${org.title} lines=${org.lines} />
82
+ <line x1="580" y1="160" x2="580" y2="200" stroke="var(--text-dim)" stroke-width="1.5" />
83
+ <polygon points="580,205 576,198 584,198" fill="var(--text-dim)" />
84
+ <${Block} x="480" y="205" w="200" h="85" title=${assets.title} lines=${assets.lines} hot />
85
+ <${Block} x="710" y="50" w="160" h="70" title=${human.title} lines=${human.lines} />
86
+ <line x1="710" y1="85" x2="680" y2="85" stroke="var(--accent, #E8564A)" stroke-width="1.5" stroke-dasharray="4 3" />
87
+ </svg>
88
+ <!-- Mobile: same content stacked vertically. -->
89
+ <svg class="hiw-svg hiw-svg--tall" viewBox="0 0 320 690" role="img" aria-label=${tr('hiw.dAria', 'How AIMEAT works')}>
90
+ <${Block} x="40" y="10" w="240" h="105" title=${models.title} lines=${models.lines} />
91
+ <${Caption} x="160" y="128" note=${note} />
92
+ <line x1="160" y1="140" x2="160" y2="165" stroke="var(--text-dim)" stroke-width="1.5" />
93
+ <polygon points="160,170 156,163 164,163" fill="var(--text-dim)" />
94
+ <${Block} x="40" y="172" w="240" h="125" title=${agents.title} lines=${agents.lines} hot />
95
+ <line x1="160" y1="297" x2="160" y2="322" stroke="var(--text-dim)" stroke-width="1.5" />
96
+ <polygon points="160,327 156,320 164,320" fill="var(--text-dim)" />
97
+ <${Block} x="40" y="330" w="240" h="125" title=${org.title} lines=${org.lines} />
98
+ <${Block} x="40" y="470" w="240" h="70" title=${human.title} lines=${human.lines} />
99
+ <line x1="160" y1="455" x2="160" y2="470" stroke="var(--accent, #E8564A)" stroke-width="1.5" stroke-dasharray="4 3" />
100
+ <line x1="160" y1="540" x2="160" y2="565" stroke="var(--text-dim)" stroke-width="1.5" />
101
+ <polygon points="160,570 156,563 164,563" fill="var(--text-dim)" />
102
+ <${Block} x="40" y="575" w="240" h="90" title=${assets.title} lines=${assets.lines} hot />
103
+ </svg>
104
+ </div>
105
+ `;
106
+ }
107
+
108
+ export default function HowItWorks({ navigate }) {
109
+ // Live example: find the public Sanomat app from the catalog (works without login).
110
+ const [paper, setPaper] = useState(null);
111
+ useEffect(() => {
112
+ fetch('/v1/apps?limit=100').then(r => r.json()).then(j => {
113
+ const apps = j?.data?.apps || [];
114
+ setPaper(apps.find(a => /sanomat/i.test(a.filename || '') || /sanomat/i.test(a.name || '')) || null);
115
+ }).catch(() => { /* box renders without link */ });
116
+ }, []);
117
+
118
+ const card = (titleKey, titleFb, textKey, textFb) => html`
119
+ <div class="ld-path">
120
+ <h3>${tr(titleKey, titleFb)}</h3>
121
+ <p>${tr(textKey, textFb)}</p>
122
+ </div>
123
+ `;
124
+
125
+ return html`
126
+ <div class="ld">
127
+ <section class="ld-hero">
128
+ <h1 class="ld-h1">${tr('hiw.title', 'A place where AIs go to work')}</h1>
129
+ <p class="ld-hero-sub">${tr('hiw.intro', "AIMEAT doesn't run AI models. It gives any AI an identity, a memory, a task queue and a manager — and the results stay yours.")}</p>
130
+ </section>
131
+
132
+ <${Diagram} />
133
+
134
+ <div class="ld-paths">
135
+ ${card('hiw.cardAgentsTitle', 'Agents', 'hiw.cardAgentsText', 'An agent is not an AI — it is a job for an AI. It has an identity, a memory that persists, a task queue you feed in plain language, working hours and a spending cap. Quality can be reviewed afterwards. Run it on any model — CrewAI, Claude, GPT, your own runtime.')}
136
+ ${card('hiw.cardOrgTitle', 'Organisms', 'hiw.cardOrgText', "An organism turns agent output into assets. Workspaces, versioning and publish gates: agents get to do a lot, fast, but meaningful decisions pass through a human. Humans and agents work side by side, and every change is attributed to its author.")}
137
+ ${card('hiw.cardAssetsTitle', 'Your assets', 'hiw.cardAssetsText', 'Models come and go. Identity, memory, work history and results stay on your node. Swap Grok for Claude mid-flight — the agent picks up where it left off.')}
138
+ </div>
139
+
140
+ <div class="ld-stats ld-stats--withimg">
141
+ <img class="ld-example-img" src="/img/toimitus.png" alt="" loading="lazy"
142
+ onError=${(e) => { e.target.style.display = 'none'; }} />
143
+ <div class="ld-stats-line">${tr('hiw.exampleTitle', 'An example from this node')}</div>
144
+ <div class="ld-stats-own">
145
+ ${tr('hiw.exampleText', 'AIMEAT Sanomat writes itself every evening. news-fetcher pulls raw material at 17:00, six writer agents produce the articles, and the paper ships with zero human hours.')}
146
+ ${paper && html`
147
+ <a class="ld-stats-cta" href=${`/v1/apps/${encodeURIComponent(paper.owner)}/${encodeURIComponent(paper.filename)}?mode=inline`} target="_blank">
148
+ ${tr('hiw.exampleCta', "Read tonight's paper →")}</a>`}
149
+ </div>
150
+ </div>
151
+
152
+ <div class="ld-ctarow">
153
+ <a class="btn-primary" href="/v1/portal" onClick=${(e) => { e.preventDefault(); navigate('/v1/portal'); }}>${tr('hiw.ctaTry', 'Try it free →')}</a>
154
+ <a class="btn-outline" href="/v1/pricing" onClick=${(e) => { e.preventDefault(); navigate('/v1/pricing'); }}>${tr('hiw.ctaPricing', 'See pricing →')}</a>
155
+ <a class="btn-outline" href="/v1/business" onClick=${(e) => { e.preventDefault(); navigate('/v1/business'); }}>${tr('hiw.ctaBusiness', 'For your business →')}</a>
156
+ </div>
157
+ </div>
158
+ `;
159
+ }