@shortlink-org/portolan 0.1.0 → 0.2.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 (381) hide show
  1. package/README.md +69 -205
  2. package/cli/init.mjs +285 -0
  3. package/cli/init.test.mjs +158 -0
  4. package/cli/portolan.mjs +37 -73
  5. package/index.html +19 -0
  6. package/package.json +5 -10
  7. package/plugins/README.md +66 -27
  8. package/plugins/portolan-go.wasm +0 -0
  9. package/portolan.json +52 -73
  10. package/public/landing/cats/bug-hunter.webp +0 -0
  11. package/public/landing/cats/compass-nap.webp +0 -0
  12. package/public/landing/cats/diagrammer.webp +0 -0
  13. package/public/landing/cats/docs-reader.webp +0 -0
  14. package/public/landing/cats/laptop.webp +0 -0
  15. package/public/landing/cats/map-inspector.webp +0 -0
  16. package/public/landing/cats/server-break.webp +0 -0
  17. package/public/landing/cats/star-mapper.webp +0 -0
  18. package/public/landing/cats/system-builder.webp +0 -0
  19. package/public/landing/cats/thread-tangle.webp +0 -0
  20. package/public/og.png +0 -0
  21. package/public/readme-header.webp +0 -0
  22. package/schema/portolan.schema.json +180 -0
  23. package/scripts/builtin-plugins.mjs +14 -0
  24. package/scripts/delivery-presets.mjs +353 -0
  25. package/scripts/gen.mjs +37 -4
  26. package/scripts/history.mjs +126 -0
  27. package/scripts/history.test.mjs +110 -0
  28. package/scripts/host-plugins/fetch-bsr.mjs +351 -0
  29. package/{plugins/fetch-bsr/options.schema.json → scripts/host-plugins/fetch-bsr.options.json} +4 -0
  30. package/scripts/host-plugins/fetch-bsr.test.mjs +212 -0
  31. package/scripts/host-plugins/fetch-csr.mjs +386 -0
  32. package/scripts/host-plugins/fetch-csr.test.mjs +178 -0
  33. package/scripts/host-plugins/fetch-git.mjs +379 -0
  34. package/scripts/host-plugins/fetch-git.test.mjs +210 -0
  35. package/scripts/local-api.mjs +22 -4
  36. package/scripts/local-api.test.mjs +84 -2
  37. package/scripts/manifest.mjs +3 -2
  38. package/scripts/package-smoke.mjs +6 -2
  39. package/scripts/plugin-host.mjs +77 -18
  40. package/scripts/plugin-host.test.mjs +50 -1
  41. package/scripts/plugin-wasm-worker.mjs +6 -3
  42. package/scripts/run-builtin.mjs +6 -26
  43. package/scripts/schema.mjs +6 -1
  44. package/src/app/App.tsx +24 -429
  45. package/src/app/Breadcrumbs.test.ts +7 -0
  46. package/src/app/Breadcrumbs.tsx +15 -2
  47. package/src/app/CatalogApp.tsx +436 -0
  48. package/src/app/Sidebar.tsx +0 -1
  49. package/src/chat/ChatPanel.tsx +98 -41
  50. package/src/chat/Composer.tsx +4 -2
  51. package/src/chat/Conversation.tsx +79 -15
  52. package/src/chat/Header.tsx +43 -9
  53. package/src/chat/Starter.tsx +64 -3
  54. package/src/chat/Steps.tsx +1 -1
  55. package/src/chat/flags.test.ts +51 -14
  56. package/src/chat/flags.ts +8 -2
  57. package/src/chat/page-context.test.ts +51 -0
  58. package/src/chat/page-context.ts +128 -0
  59. package/src/chat/prompt.test.ts +49 -1
  60. package/src/chat/prompt.ts +42 -1
  61. package/src/chat/tools.ts +7 -2
  62. package/src/chat/transport.ts +16 -4
  63. package/src/components/ProblemRow.tsx +182 -0
  64. package/src/data.ts +20 -5
  65. package/src/er/ErCanvas.tsx +1 -1
  66. package/src/graph/DependencyGraph.tsx +10 -1
  67. package/src/graph/FocusedEventGraph.tsx +1 -1
  68. package/src/graph/GraphToolbar.tsx +2 -4
  69. package/src/index.css +220 -2
  70. package/src/landing/DraggableReveal.tsx +168 -0
  71. package/src/landing/EstateGraph.tsx +28 -0
  72. package/src/landing/FlowPlayback.tsx +282 -0
  73. package/src/landing/HeroMap.tsx +128 -0
  74. package/src/landing/LandingPage.tsx +757 -0
  75. package/src/landing/ProductFrame.tsx +68 -0
  76. package/src/landing/ProductTour.tsx +371 -0
  77. package/src/landing/catalog.ts +25 -0
  78. package/src/landing/motion.tsx +52 -0
  79. package/src/lib/all-problems.ts +28 -0
  80. package/src/lib/local-api.ts +35 -1
  81. package/src/lib/setup-info.test.ts +8 -0
  82. package/src/lib/setup-info.ts +3 -2
  83. package/src/lib/source-code.ts +2 -1
  84. package/src/likec4/C4View.tsx +5 -0
  85. package/src/likec4/InteractiveView.tsx +5 -1
  86. package/src/map/ContextMapGraph.tsx +47 -20
  87. package/src/pages/ContextMap.tsx +5 -1
  88. package/src/pages/FlowDetail.tsx +24 -7
  89. package/src/pages/Overview.tsx +68 -12
  90. package/src/pages/Problems.tsx +6 -193
  91. package/src/pages/Settings.tsx +133 -108
  92. package/src/pages/settings/DeliverySettings.tsx +229 -0
  93. package/src/pages/settings/PreferencesSettings.tsx +101 -0
  94. package/src/routes.test.ts +20 -0
  95. package/src/routes.ts +13 -1
  96. package/vite.config.ts +12 -8
  97. package/catalog/enum_test.go +0 -46
  98. package/catalog/model.go +0 -932
  99. package/catalog/roundtrip_test.go +0 -185
  100. package/catalog/via_test.go +0 -38
  101. package/go.mod +0 -14
  102. package/go.sum +0 -14
  103. package/internal/commands/cargo.go +0 -154
  104. package/internal/commands/commands.go +0 -143
  105. package/internal/commands/commands_test.go +0 -323
  106. package/internal/commands/gradle.go +0 -77
  107. package/internal/commands/justfile.go +0 -82
  108. package/internal/commands/makefile.go +0 -106
  109. package/internal/commands/maven.go +0 -93
  110. package/internal/commands/packagejson.go +0 -159
  111. package/internal/commands/pyproject.go +0 -169
  112. package/internal/commands/taskfile.go +0 -101
  113. package/internal/commands/testdata/estate/.cargo/config.toml +0 -7
  114. package/internal/commands/testdata/estate/Makefile +0 -33
  115. package/internal/commands/testdata/estate/Taskfile.yml +0 -21
  116. package/internal/commands/testdata/estate/build.gradle.kts +0 -21
  117. package/internal/commands/testdata/estate/justfile +0 -20
  118. package/internal/commands/testdata/estate/package.json +0 -13
  119. package/internal/commands/testdata/estate/pom.xml +0 -29
  120. package/internal/commands/testdata/estate/pyproject.toml +0 -27
  121. package/internal/commands/testdata/estate/xtask/src/main.rs +0 -27
  122. package/internal/commands/testdata/golden/commands.json +0 -305
  123. package/internal/gohttp/analyze.go +0 -2500
  124. package/internal/gohttp/endpoints.go +0 -1067
  125. package/internal/gohttp/roots.go +0 -320
  126. package/internal/gohttp/typed.go +0 -143
  127. package/internal/goscan/constants.go +0 -85
  128. package/internal/goscan/goscan_test.go +0 -227
  129. package/internal/goscan/names.go +0 -52
  130. package/internal/goscan/parse_test.go +0 -11
  131. package/internal/goscan/source.go +0 -36
  132. package/internal/goscan/tree.go +0 -155
  133. package/internal/goscan/types.go +0 -99
  134. package/internal/wsdl/ids.go +0 -127
  135. package/internal/wsdl/ids_test.go +0 -21
  136. package/internal/wsdl/model.go +0 -62
  137. package/internal/wsdl/parse.go +0 -920
  138. package/internal/wsdl/parse_test.go +0 -133
  139. package/plugin/describe.go +0 -107
  140. package/plugin/describe_test.go +0 -114
  141. package/plugin/protocol.go +0 -117
  142. package/plugin/schematest/schematest.go +0 -126
  143. package/plugins/extract-adr/describe.go +0 -19
  144. package/plugins/extract-adr/describe_test.go +0 -11
  145. package/plugins/extract-adr/extract.go +0 -153
  146. package/plugins/extract-adr/extract_test.go +0 -350
  147. package/plugins/extract-adr/history.go +0 -99
  148. package/plugins/extract-adr/main.go +0 -65
  149. package/plugins/extract-adr/parse.go +0 -642
  150. package/plugins/extract-adr/parse_test.go +0 -405
  151. package/plugins/extract-asyncapi/describe.go +0 -19
  152. package/plugins/extract-asyncapi/describe_test.go +0 -11
  153. package/plugins/extract-asyncapi/extract.go +0 -315
  154. package/plugins/extract-asyncapi/extract_test.go +0 -197
  155. package/plugins/extract-asyncapi/main.go +0 -48
  156. package/plugins/extract-asyncapi/spec.go +0 -151
  157. package/plugins/extract-commands/describe.go +0 -19
  158. package/plugins/extract-commands/describe_test.go +0 -11
  159. package/plugins/extract-commands/extract.go +0 -79
  160. package/plugins/extract-commands/extract_test.go +0 -92
  161. package/plugins/extract-commands/main.go +0 -55
  162. package/plugins/extract-csr/avro.go +0 -258
  163. package/plugins/extract-csr/describe.go +0 -19
  164. package/plugins/extract-csr/describe_test.go +0 -11
  165. package/plugins/extract-csr/extract.go +0 -338
  166. package/plugins/extract-csr/extract_test.go +0 -374
  167. package/plugins/extract-csr/jsonschema.go +0 -343
  168. package/plugins/extract-csr/lock.go +0 -25
  169. package/plugins/extract-csr/main.go +0 -84
  170. package/plugins/extract-csr/subject.go +0 -102
  171. package/plugins/extract-flows/describe.go +0 -19
  172. package/plugins/extract-flows/describe_test.go +0 -11
  173. package/plugins/extract-flows/extract.go +0 -91
  174. package/plugins/extract-flows/main.go +0 -45
  175. package/plugins/extract-flows/parse.go +0 -593
  176. package/plugins/extract-flows/parse_test.go +0 -204
  177. package/plugins/extract-glossary/describe.go +0 -19
  178. package/plugins/extract-glossary/describe_test.go +0 -11
  179. package/plugins/extract-glossary/extract.go +0 -115
  180. package/plugins/extract-glossary/extract_test.go +0 -220
  181. package/plugins/extract-glossary/main.go +0 -59
  182. package/plugins/extract-glossary/parse.go +0 -214
  183. package/plugins/extract-glossary/parse_test.go +0 -203
  184. package/plugins/extract-go/aggregate.go +0 -214
  185. package/plugins/extract-go/client.go +0 -409
  186. package/plugins/extract-go/client_test.go +0 -266
  187. package/plugins/extract-go/describe.go +0 -19
  188. package/plugins/extract-go/describe_test.go +0 -11
  189. package/plugins/extract-go/enum.go +0 -195
  190. package/plugins/extract-go/enum_test.go +0 -82
  191. package/plugins/extract-go/event.go +0 -99
  192. package/plugins/extract-go/extract.go +0 -191
  193. package/plugins/extract-go/extract_test.go +0 -261
  194. package/plugins/extract-go/flow.go +0 -1441
  195. package/plugins/extract-go/flow_test.go +0 -609
  196. package/plugins/extract-go/httpclient.go +0 -174
  197. package/plugins/extract-go/httpclient_test.go +0 -296
  198. package/plugins/extract-go/ids.go +0 -92
  199. package/plugins/extract-go/layout.go +0 -225
  200. package/plugins/extract-go/layout_test.go +0 -125
  201. package/plugins/extract-go/lifecycle.go +0 -324
  202. package/plugins/extract-go/lifecycle_test.go +0 -108
  203. package/plugins/extract-go/main.go +0 -79
  204. package/plugins/extract-go/operation.go +0 -157
  205. package/plugins/extract-go/source.go +0 -316
  206. package/plugins/extract-go/transport.go +0 -321
  207. package/plugins/extract-go/transport_test.go +0 -145
  208. package/plugins/extract-go/wiring.go +0 -434
  209. package/plugins/extract-go-nats/describe.go +0 -19
  210. package/plugins/extract-go-nats/describe_test.go +0 -11
  211. package/plugins/extract-go-nats/extract.go +0 -177
  212. package/plugins/extract-go-nats/extract_test.go +0 -389
  213. package/plugins/extract-go-nats/index.go +0 -379
  214. package/plugins/extract-go-nats/main.go +0 -42
  215. package/plugins/extract-go-nats/resolve.go +0 -161
  216. package/plugins/extract-go-nats/sites.go +0 -224
  217. package/plugins/extract-graphql/describe.go +0 -19
  218. package/plugins/extract-graphql/describe_test.go +0 -11
  219. package/plugins/extract-graphql/extract.go +0 -433
  220. package/plugins/extract-graphql/extract_test.go +0 -256
  221. package/plugins/extract-graphql/ids.go +0 -49
  222. package/plugins/extract-graphql/lex.go +0 -237
  223. package/plugins/extract-graphql/main.go +0 -51
  224. package/plugins/extract-graphql/parse.go +0 -621
  225. package/plugins/extract-graphql/parse_test.go +0 -122
  226. package/plugins/extract-http-clients/describe.go +0 -19
  227. package/plugins/extract-http-clients/describe_test.go +0 -11
  228. package/plugins/extract-http-clients/extract.go +0 -705
  229. package/plugins/extract-http-clients/extract_test.go +0 -1263
  230. package/plugins/extract-http-clients/main.go +0 -42
  231. package/plugins/extract-openapi/describe.go +0 -19
  232. package/plugins/extract-openapi/describe_test.go +0 -11
  233. package/plugins/extract-openapi/discover.go +0 -243
  234. package/plugins/extract-openapi/extract.go +0 -526
  235. package/plugins/extract-openapi/extract_test.go +0 -545
  236. package/plugins/extract-openapi/main.go +0 -75
  237. package/plugins/extract-openapi/spec.go +0 -350
  238. package/plugins/extract-project/describe.go +0 -19
  239. package/plugins/extract-project/describe_test.go +0 -11
  240. package/plugins/extract-project/extract.go +0 -221
  241. package/plugins/extract-project/extract_test.go +0 -109
  242. package/plugins/extract-project/main.go +0 -41
  243. package/plugins/extract-proto/ast.go +0 -125
  244. package/plugins/extract-proto/consumes.go +0 -77
  245. package/plugins/extract-proto/describe.go +0 -19
  246. package/plugins/extract-proto/extract.go +0 -293
  247. package/plugins/extract-proto/extract_test.go +0 -459
  248. package/plugins/extract-proto/ids.go +0 -89
  249. package/plugins/extract-proto/ids_test.go +0 -57
  250. package/plugins/extract-proto/lex.go +0 -285
  251. package/plugins/extract-proto/main.go +0 -100
  252. package/plugins/extract-proto/module.go +0 -120
  253. package/plugins/extract-proto/parse.go +0 -720
  254. package/plugins/extract-proto/parse_test.go +0 -307
  255. package/plugins/extract-proto/provides.go +0 -236
  256. package/plugins/extract-proto/resolve.go +0 -222
  257. package/plugins/extract-proto/resolve_test.go +0 -119
  258. package/plugins/extract-redis/describe.go +0 -19
  259. package/plugins/extract-redis/describe_test.go +0 -11
  260. package/plugins/extract-redis/extract.go +0 -183
  261. package/plugins/extract-redis/extract_test.go +0 -168
  262. package/plugins/extract-redis/keyspaces.go +0 -469
  263. package/plugins/extract-redis/main.go +0 -44
  264. package/plugins/extract-river/describe.go +0 -19
  265. package/plugins/extract-river/describe_test.go +0 -11
  266. package/plugins/extract-river/extract.go +0 -507
  267. package/plugins/extract-river/extract_test.go +0 -132
  268. package/plugins/extract-river/main.go +0 -42
  269. package/plugins/extract-sql/ddl.go +0 -893
  270. package/plugins/extract-sql/ddl_test.go +0 -401
  271. package/plugins/extract-sql/describe.go +0 -19
  272. package/plugins/extract-sql/describe_test.go +0 -11
  273. package/plugins/extract-sql/layout.go +0 -221
  274. package/plugins/extract-sql/layout_test.go +0 -55
  275. package/plugins/extract-sql/lineage.go +0 -107
  276. package/plugins/extract-sql/main.go +0 -141
  277. package/plugins/extract-sql/maps.go +0 -564
  278. package/plugins/extract-sql/maps_java.go +0 -117
  279. package/plugins/extract-sql/maps_rust.go +0 -333
  280. package/plugins/extract-sql/maps_rust_test.go +0 -70
  281. package/plugins/extract-sql/maps_test.go +0 -204
  282. package/plugins/extract-sql/maps_ts.go +0 -398
  283. package/plugins/extract-sql/maps_ts_test.go +0 -136
  284. package/plugins/extract-sql/projection.go +0 -70
  285. package/plugins/extract-sql/projection_test.go +0 -50
  286. package/plugins/extract-sql/store.go +0 -420
  287. package/plugins/extract-sql/store_test.go +0 -233
  288. package/plugins/extract-sql/view.go +0 -295
  289. package/plugins/extract-watermill/describe.go +0 -19
  290. package/plugins/extract-watermill/describe_test.go +0 -11
  291. package/plugins/extract-watermill/extract.go +0 -1228
  292. package/plugins/extract-watermill/extract_test.go +0 -234
  293. package/plugins/extract-watermill/main.go +0 -41
  294. package/plugins/extract-wsdl/describe.go +0 -19
  295. package/plugins/extract-wsdl/describe_test.go +0 -11
  296. package/plugins/extract-wsdl/extract.go +0 -148
  297. package/plugins/extract-wsdl/extract_test.go +0 -45
  298. package/plugins/extract-wsdl/main.go +0 -53
  299. package/plugins/fetch-bsr/auth.go +0 -114
  300. package/plugins/fetch-bsr/bsr.go +0 -240
  301. package/plugins/fetch-bsr/cache.go +0 -66
  302. package/plugins/fetch-bsr/describe.go +0 -19
  303. package/plugins/fetch-bsr/fetch.go +0 -192
  304. package/plugins/fetch-bsr/fetch_test.go +0 -465
  305. package/plugins/fetch-bsr/lock.go +0 -71
  306. package/plugins/fetch-bsr/main.go +0 -117
  307. package/plugins/fetch-csr/auth.go +0 -95
  308. package/plugins/fetch-csr/cache.go +0 -69
  309. package/plugins/fetch-csr/describe.go +0 -19
  310. package/plugins/fetch-csr/describe_test.go +0 -11
  311. package/plugins/fetch-csr/fetch.go +0 -254
  312. package/plugins/fetch-csr/fetch_test.go +0 -494
  313. package/plugins/fetch-csr/lock.go +0 -95
  314. package/plugins/fetch-csr/main.go +0 -122
  315. package/plugins/fetch-csr/registry.go +0 -209
  316. package/plugins/fetch-git/cache.go +0 -65
  317. package/plugins/fetch-git/describe.go +0 -19
  318. package/plugins/fetch-git/describe_test.go +0 -11
  319. package/plugins/fetch-git/fetch.go +0 -181
  320. package/plugins/fetch-git/fetch_test.go +0 -332
  321. package/plugins/fetch-git/git.go +0 -169
  322. package/plugins/fetch-git/lock.go +0 -72
  323. package/plugins/fetch-git/main.go +0 -127
  324. package/plugins/fetch-git/offline.go +0 -39
  325. package/plugins/fetch-git/pin.go +0 -90
  326. package/plugins/fetch-git/pin_test.go +0 -74
  327. package/plugins/gen-backstage/describe.go +0 -17
  328. package/plugins/gen-backstage/main.go +0 -22
  329. package/plugins/gen-backstage/plugin.go +0 -473
  330. package/plugins/gen-backstage/plugin_test.go +0 -145
  331. package/plugins/gen-backstage.wasm +0 -0
  332. package/plugins/gen-markdown/adr.go +0 -162
  333. package/plugins/gen-markdown/aggregate.go +0 -362
  334. package/plugins/gen-markdown/canonical.go +0 -93
  335. package/plugins/gen-markdown/context.go +0 -90
  336. package/plugins/gen-markdown/coverage_test.go +0 -89
  337. package/plugins/gen-markdown/describe.go +0 -19
  338. package/plugins/gen-markdown/describe_test.go +0 -11
  339. package/plugins/gen-markdown/external.go +0 -71
  340. package/plugins/gen-markdown/flow.go +0 -277
  341. package/plugins/gen-markdown/glossary.go +0 -76
  342. package/plugins/gen-markdown/glossary_test.go +0 -148
  343. package/plugins/gen-markdown/llms.go +0 -302
  344. package/plugins/gen-markdown/main.go +0 -22
  345. package/plugins/gen-markdown/markdown.go +0 -254
  346. package/plugins/gen-markdown/markdown_test.go +0 -100
  347. package/plugins/gen-markdown/module.go +0 -107
  348. package/plugins/gen-markdown/plugin.go +0 -39
  349. package/plugins/gen-markdown/quality_test.go +0 -176
  350. package/plugins/gen-markdown/redis_test.go +0 -38
  351. package/plugins/gen-markdown/render.go +0 -344
  352. package/plugins/gen-markdown/render_test.go +0 -186
  353. package/plugins/gen-markdown/service.go +0 -456
  354. package/plugins/gen-markdown/source.go +0 -177
  355. package/plugins/gen-markdown/store.go +0 -201
  356. package/plugins/gen-markdown.wasm +0 -0
  357. package/plugins/gen-mermaid/describe.go +0 -17
  358. package/plugins/gen-mermaid/main.go +0 -22
  359. package/plugins/gen-mermaid/plugin.go +0 -104
  360. package/plugins/gen-mermaid/plugin_test.go +0 -33
  361. package/plugins/gen-mermaid.wasm +0 -0
  362. package/plugins/openapi/ids.go +0 -261
  363. package/plugins/openapi/ids_test.go +0 -98
  364. package/plugins/verify-codeowners/describe.go +0 -19
  365. package/plugins/verify-codeowners/describe_test.go +0 -11
  366. package/plugins/verify-codeowners/main.go +0 -75
  367. package/plugins/verify-codeowners/match.go +0 -85
  368. package/plugins/verify-codeowners/match_test.go +0 -47
  369. package/plugins/verify-codeowners/owners.go +0 -164
  370. package/plugins/verify-codeowners/owners_test.go +0 -225
  371. package/plugins/verify-codeowners/parse.go +0 -90
  372. package/plugins/verify-codeowners/parse_test.go +0 -62
  373. package/plugins/verify-otel/describe.go +0 -19
  374. package/plugins/verify-otel/describe_test.go +0 -11
  375. package/plugins/verify-otel/main.go +0 -53
  376. package/plugins/verify-otel/match.go +0 -336
  377. package/plugins/verify-otel/otlp.go +0 -200
  378. package/plugins/verify-otel/verify.go +0 -734
  379. package/plugins/verify-otel/verify_test.go +0 -460
  380. /package/{plugins/fetch-csr/options.schema.json → scripts/host-plugins/fetch-csr.options.json} +0 -0
  381. /package/{plugins/fetch-git/options.schema.json → scripts/host-plugins/fetch-git.options.json} +0 -0
package/src/index.css CHANGED
@@ -32,6 +32,12 @@
32
32
  --accent-lo: #1a5ec9;
33
33
  --accent-hi: #3d86f5;
34
34
 
35
+ /* Product action, deliberately separate from event amber. In the light
36
+ theme it gives onboarding and AI entry points a warmer invitation without
37
+ teaching readers that an event/status colour means “click me”. */
38
+ --product-action: #c76a1b;
39
+ --product-action-ink: #6f350a;
40
+
35
41
  /* Source preview syntax. These are semantic roles rather than one imported
36
42
  editor theme, so code follows Portolan's light and dark palettes. */
37
43
  --syntax-comment: #6a737d;
@@ -149,6 +155,8 @@
149
155
  --accent: #5b9dff;
150
156
  --accent-lo: #3d7fe0;
151
157
  --accent-hi: #7ab2ff;
158
+ --product-action: #5b9dff;
159
+ --product-action-ink: #e3e8ee;
152
160
 
153
161
  --syntax-comment: #8b97a4;
154
162
  --syntax-keyword: #c6a0f6;
@@ -574,23 +582,147 @@
574
582
  .btn-accent:hover {
575
583
  filter: brightness(1.06);
576
584
  }
585
+
586
+ /* The landing's primary action should feel like a control from the map,
587
+ not a bright rectangle pasted over it. The depth is always present; the
588
+ brief chart-line sweep only appears when a reader points at the action. */
589
+ .landing-primary,
590
+ .product-primary {
591
+ @apply mono flex items-center gap-1.5 rounded-control px-3 py-1.5;
592
+ position: relative;
593
+ isolation: isolate;
594
+ overflow: hidden;
595
+ color: var(--fg);
596
+ border: 1px solid color-mix(in srgb, var(--accent) 48%, var(--border));
597
+ background:
598
+ radial-gradient(
599
+ circle at 18% 0%,
600
+ rgb(255 255 255 / 0.1),
601
+ transparent 42%
602
+ ),
603
+ linear-gradient(
604
+ to bottom,
605
+ color-mix(in srgb, var(--surface-2) 76%, var(--accent)),
606
+ color-mix(in srgb, var(--surface) 84%, var(--accent))
607
+ );
608
+ box-shadow:
609
+ inset 0 1px 0 rgb(255 255 255 / 0.18),
610
+ inset 0 -1px 0 rgb(0 0 0 / 0.2),
611
+ 0 1px 2px rgb(0 0 0 / 0.2),
612
+ 0 12px 28px -18px color-mix(in srgb, var(--accent) 82%, transparent);
613
+ text-shadow: 0 1px 0 color-mix(in srgb, var(--bg) 55%, transparent);
614
+ transition:
615
+ transform var(--dur-micro) var(--ease-out),
616
+ border-color var(--dur-micro) var(--ease-out),
617
+ box-shadow var(--dur-panel) var(--ease-out);
618
+ }
619
+ :root:not(.dark) :is(.landing-primary, .product-primary) {
620
+ color: var(--product-action-ink);
621
+ border-color: color-mix(in srgb, var(--product-action) 52%, var(--border));
622
+ background:
623
+ radial-gradient(
624
+ circle at 18% 0%,
625
+ rgb(255 255 255 / 0.68),
626
+ transparent 44%
627
+ ),
628
+ linear-gradient(
629
+ to bottom,
630
+ color-mix(in srgb, var(--product-action) 13%, white),
631
+ color-mix(in srgb, var(--product-action) 28%, white)
632
+ );
633
+ box-shadow:
634
+ inset 0 1px 0 rgb(255 255 255 / 0.82),
635
+ inset 0 -1px 0 color-mix(in srgb, var(--product-action) 22%, transparent),
636
+ 0 1px 2px rgb(91 45 8 / 0.1),
637
+ 0 12px 28px -18px color-mix(in srgb, var(--product-action) 70%, transparent);
638
+ text-shadow: 0 1px 0 rgb(255 255 255 / 0.55);
639
+ }
640
+ :is(.landing-primary, .product-primary)::before {
641
+ position: absolute;
642
+ inset: -1px;
643
+ z-index: 0;
644
+ pointer-events: none;
645
+ content: "";
646
+ background: linear-gradient(
647
+ 112deg,
648
+ transparent 28%,
649
+ rgb(255 255 255 / 0.03) 39%,
650
+ rgb(255 255 255 / 0.3) 48%,
651
+ rgb(255 255 255 / 0.05) 57%,
652
+ transparent 70%
653
+ );
654
+ transform: translateX(-135%);
655
+ transition: transform 600ms var(--ease-out);
656
+ }
657
+ :is(.landing-primary, .product-primary):hover {
658
+ filter: none;
659
+ border-color: color-mix(in srgb, var(--accent) 82%, white);
660
+ box-shadow:
661
+ inset 0 1px 0 rgb(255 255 255 / 0.24),
662
+ inset 0 -1px 0 rgb(0 0 0 / 0.2),
663
+ 0 2px 4px rgb(0 0 0 / 0.22),
664
+ 0 16px 34px -17px color-mix(in srgb, var(--accent) 95%, transparent);
665
+ transform: translateY(-1px);
666
+ }
667
+ :root:not(.dark) :is(.landing-primary, .product-primary):hover {
668
+ border-color: color-mix(in srgb, var(--product-action) 78%, white);
669
+ box-shadow:
670
+ inset 0 1px 0 rgb(255 255 255 / 0.9),
671
+ inset 0 -1px 0 color-mix(in srgb, var(--product-action) 25%, transparent),
672
+ 0 2px 4px rgb(91 45 8 / 0.12),
673
+ 0 16px 34px -17px color-mix(in srgb, var(--product-action) 76%, transparent);
674
+ }
675
+ :is(.landing-primary, .product-primary):hover::before {
676
+ transform: translateX(135%);
677
+ }
678
+ .landing-primary svg:last-child {
679
+ transition: transform var(--dur-micro) var(--ease-out);
680
+ }
681
+ .landing-primary:hover svg:last-child {
682
+ transform: translateX(2px);
683
+ }
684
+ :is(.landing-primary, .product-primary):active {
685
+ transform: translateY(0);
686
+ }
687
+ :is(.landing-primary, .product-primary):focus-visible {
688
+ outline: 2px solid color-mix(in srgb, var(--accent) 55%, white);
689
+ outline-offset: 3px;
690
+ }
577
691
  .btn-accent:disabled,
692
+ .product-primary:disabled,
578
693
  .tbtn:disabled {
579
694
  cursor: not-allowed;
580
695
  opacity: 0.55;
581
696
  }
582
697
 
698
+ @media (prefers-reduced-motion: reduce) {
699
+ .landing-primary,
700
+ .product-primary,
701
+ .landing-primary::before,
702
+ .product-primary::before,
703
+ .landing-primary svg:last-child {
704
+ transition: none;
705
+ }
706
+ .landing-primary:hover,
707
+ .product-primary:hover,
708
+ .landing-primary:hover svg:last-child {
709
+ transform: none;
710
+ }
711
+ }
712
+
583
713
  /* A group of related controls reads as one object: a single border around
584
714
  the set, hairlines only between its members. */
585
715
  .seg {
586
- @apply flex items-center overflow-hidden rounded-control border border-line;
716
+ @apply flex shrink-0 items-center overflow-hidden rounded-control border border-line;
587
717
  }
588
718
  .seg > * + * {
589
719
  border-left: 1px solid var(--border);
590
720
  }
721
+ /* A member never breaks its line: a toolbar that runs out of room wraps
722
+ whole segments, not the word inside one. */
591
723
  .seg > button,
592
724
  .seg > a {
593
- @apply mono px-2 py-1 text-muted;
725
+ @apply mono px-2 py-1 whitespace-nowrap text-muted;
594
726
  transition:
595
727
  background-color var(--dur-micro) var(--ease-out),
596
728
  color var(--dur-micro) var(--ease-out);
@@ -654,6 +786,68 @@
654
786
  background: color-mix(in srgb, currentColor 12%, transparent);
655
787
  }
656
788
 
789
+ /* Landing-only presentation surfaces. They reuse the application's canvas
790
+ and status tokens, but carry no catalog data or diagram runtime. */
791
+ .landing-grid {
792
+ background-color: var(--flow-pane);
793
+ background-image: radial-gradient(var(--flow-dot) 1px, transparent 1px);
794
+ background-size: 18px 18px;
795
+ }
796
+ .landing-product-shadow {
797
+ box-shadow:
798
+ 0 0 0 1px color-mix(in srgb, var(--accent) 8%, transparent),
799
+ 0 28px 80px -34px color-mix(in srgb, var(--accent) 42%, transparent),
800
+ var(--shadow-md);
801
+ }
802
+ .landing-hero {
803
+ background-image: radial-gradient(
804
+ 70% 85% at 72% 22%,
805
+ color-mix(in srgb, var(--accent) 9%, transparent),
806
+ transparent 70%
807
+ );
808
+ }
809
+ .landing-final {
810
+ background-image: radial-gradient(
811
+ 55% 100% at 50% 100%,
812
+ color-mix(in srgb, var(--accent) 10%, transparent),
813
+ transparent 72%
814
+ );
815
+ }
816
+ .landing-orbit {
817
+ position: absolute;
818
+ border: 1px solid color-mix(in srgb, var(--accent) 10%, transparent);
819
+ border-radius: 9999px;
820
+ pointer-events: none;
821
+ }
822
+ .landing-orbit-one {
823
+ width: 700px;
824
+ height: 700px;
825
+ top: -430px;
826
+ right: -140px;
827
+ }
828
+ .landing-orbit-two {
829
+ width: 980px;
830
+ height: 980px;
831
+ top: -570px;
832
+ right: -280px;
833
+ }
834
+ .landing-hero .landing-canvas .react-flow__edge-path {
835
+ stroke-width: 1.75 !important;
836
+ }
837
+ .landing-hero-title {
838
+ line-height: 0.96 !important;
839
+ }
840
+ @media (min-width: 1024px) and (max-height: 760px) {
841
+ .landing-hero-grid {
842
+ padding-top: 2rem !important;
843
+ padding-bottom: 2rem !important;
844
+ }
845
+ .landing-hero-title {
846
+ font-size: clamp(3.5rem, 4.8vw, 3.9rem) !important;
847
+ line-height: 0.94 !important;
848
+ }
849
+ }
850
+
657
851
  /* Non-navigable header for a group of leaves inside an aggregate. */
658
852
  .group-row {
659
853
  @apply mono flex w-full items-center gap-1.5 py-[3px] pr-2 text-left text-muted hover:bg-surface;
@@ -1525,6 +1719,30 @@ h3 {
1525
1719
  font-family: var(--font-sans);
1526
1720
  }
1527
1721
 
1722
+ /* How a canvas of this app arrives and changes. The boxes rise in as elk
1723
+ places them and the lines follow a beat later, so the picture is drawn
1724
+ rather than dropped in over the skeleton; anything a selection, a focus or
1725
+ a playback dims fades rather than snaps. The node's own transform is its
1726
+ position, so what rises is the card inside it, never the node. Opt-in by
1727
+ class: LikeC4 draws its canvases with the same library and its own motion,
1728
+ and must not inherit this. */
1729
+ .canvas-motion .react-flow__node {
1730
+ animation: fade-in var(--dur-narrative) var(--ease-out) both;
1731
+ transition: opacity var(--dur-narrative) var(--ease-in-out);
1732
+ }
1733
+ .canvas-motion .react-flow__node > * {
1734
+ animation: row-in var(--dur-narrative) var(--ease-out) both;
1735
+ }
1736
+ .canvas-motion .react-flow__edge {
1737
+ animation: fade-in var(--dur-narrative) var(--ease-out) both;
1738
+ animation-delay: 180ms;
1739
+ }
1740
+ .canvas-motion .react-flow__edge-path {
1741
+ transition:
1742
+ opacity var(--dur-narrative) var(--ease-in-out),
1743
+ stroke-width var(--dur-micro) var(--ease-out);
1744
+ }
1745
+
1528
1746
  /* Selection is the ring, on the node's own box. The event pill is the one
1529
1747
  node that is not the shape of its box - it is a capsule, and at fit zooms
1530
1748
  it shrinks to an icon inside the box - so it draws the ring itself. */
@@ -0,0 +1,168 @@
1
+ import { useId, useState } from "react";
2
+ import type { KeyboardEvent, ReactNode } from "react";
3
+ import { animate, useDragControls, useMotionValue } from "motion/react";
4
+ import type { PanInfo } from "motion/react";
5
+ import { m } from "../lib/motion";
6
+
7
+ const cats = [
8
+ "thread-tangle.webp",
9
+ "map-inspector.webp",
10
+ "laptop.webp",
11
+ "diagrammer.webp",
12
+ "compass-nap.webp",
13
+ "docs-reader.webp",
14
+ "bug-hunter.webp",
15
+ "system-builder.webp",
16
+ "server-break.webp",
17
+ "star-mapper.webp",
18
+ ].map((file) => `${import.meta.env.BASE_URL}landing/cats/${file}`);
19
+
20
+ const X_LIMIT = 300;
21
+ const Y_LIMIT = 190;
22
+ const KEYBOARD_STEP = 24;
23
+
24
+ type RevealEdge = "top" | "right" | "bottom" | "left";
25
+
26
+ const catPlacement: Record<RevealEdge, string> = {
27
+ top: "items-start justify-center",
28
+ right: "items-center justify-end",
29
+ bottom: "items-end justify-center",
30
+ left: "items-center justify-start",
31
+ };
32
+
33
+ function clamp(value: number, min: number, max: number) {
34
+ return Math.min(max, Math.max(min, value));
35
+ }
36
+
37
+ export function DraggableReveal({
38
+ children,
39
+ handleClassName = "inset-x-0",
40
+ label = "Movable product preview",
41
+ }: {
42
+ children: ReactNode;
43
+ handleClassName?: string;
44
+ label?: string;
45
+ }) {
46
+ const controls = useDragControls();
47
+ const descriptionId = useId();
48
+ const x = useMotionValue(0);
49
+ const y = useMotionValue(0);
50
+ const [dragging, setDragging] = useState(false);
51
+ const [revealEdge, setRevealEdge] = useState<RevealEdge>("right");
52
+ const [cat] = useState(
53
+ () => cats[Math.floor(Math.random() * cats.length)] ?? cats[0],
54
+ );
55
+ const [mirrored] = useState(() => Math.random() < 0.5);
56
+
57
+ const returnToOrigin = () => {
58
+ const transition = {
59
+ duration: 0.78,
60
+ ease: [0.22, 1, 0.36, 1] as const,
61
+ };
62
+ animate(x, 0, transition);
63
+ animate(y, 0, transition);
64
+ };
65
+
66
+ const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
67
+ const moves: Partial<Record<string, [number, number]>> = {
68
+ ArrowLeft: [-KEYBOARD_STEP, 0],
69
+ ArrowRight: [KEYBOARD_STEP, 0],
70
+ ArrowUp: [0, -KEYBOARD_STEP],
71
+ ArrowDown: [0, KEYBOARD_STEP],
72
+ };
73
+ const move = moves[event.key];
74
+ if (move) {
75
+ event.preventDefault();
76
+ if (Math.abs(move[0]) > Math.abs(move[1])) {
77
+ setRevealEdge(move[0] < 0 ? "right" : "left");
78
+ } else {
79
+ setRevealEdge(move[1] < 0 ? "bottom" : "top");
80
+ }
81
+ x.set(clamp(x.get() + move[0], -X_LIMIT, X_LIMIT));
82
+ y.set(clamp(y.get() + move[1], -Y_LIMIT, Y_LIMIT));
83
+ } else if (event.key === "Escape") {
84
+ x.set(0);
85
+ y.set(0);
86
+ }
87
+ };
88
+
89
+ const onDrag = (
90
+ _event: MouseEvent | TouchEvent | PointerEvent,
91
+ info: PanInfo,
92
+ ) => {
93
+ if (Math.hypot(info.offset.x, info.offset.y) < 16) return;
94
+ if (Math.abs(info.offset.x) >= Math.abs(info.offset.y)) {
95
+ setRevealEdge(info.offset.x < 0 ? "right" : "left");
96
+ } else {
97
+ setRevealEdge(info.offset.y < 0 ? "bottom" : "top");
98
+ }
99
+ };
100
+
101
+ return (
102
+ <div className="relative isolate">
103
+ <div
104
+ aria-hidden
105
+ className={`pointer-events-none absolute inset-0 z-0 flex ${catPlacement[revealEdge]}`}
106
+ >
107
+ <m.img
108
+ layout="position"
109
+ src={cat}
110
+ alt=""
111
+ width={800}
112
+ height={800}
113
+ decoding="async"
114
+ style={{ scaleX: mirrored ? -1 : 1 }}
115
+ animate={{
116
+ opacity: dragging ? 1 : 0.84,
117
+ scale: dragging ? 1 : 0.96,
118
+ y: dragging ? -3 : 0,
119
+ }}
120
+ transition={{
121
+ layout: { type: "spring", stiffness: 260, damping: 28 },
122
+ opacity: { duration: 0.22 },
123
+ scale: { duration: 0.28 },
124
+ y: { duration: 0.28 },
125
+ }}
126
+ className="max-h-[72%] w-[36%] object-contain drop-shadow-[0_18px_26px_rgba(0,0,0,0.14)]"
127
+ />
128
+ </div>
129
+ <span id={descriptionId} className="sr-only">
130
+ Move this preview with the arrow keys. Press Escape to return it to its
131
+ starting position.
132
+ </span>
133
+ <m.div
134
+ drag
135
+ dragControls={controls}
136
+ dragListener={false}
137
+ dragConstraints={{
138
+ left: -X_LIMIT,
139
+ right: X_LIMIT,
140
+ top: -Y_LIMIT,
141
+ bottom: Y_LIMIT,
142
+ }}
143
+ dragElastic={0.08}
144
+ dragMomentum={false}
145
+ style={{ x, y }}
146
+ whileDrag={{ scale: 0.985 }}
147
+ tabIndex={0}
148
+ aria-label={label}
149
+ aria-describedby={descriptionId}
150
+ onKeyDown={onKeyDown}
151
+ onDragStart={() => setDragging(true)}
152
+ onDrag={onDrag}
153
+ onDragEnd={() => {
154
+ setDragging(false);
155
+ window.requestAnimationFrame(returnToOrigin);
156
+ }}
157
+ className="relative z-10 rounded-[18px] focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-accent"
158
+ >
159
+ {children}
160
+ <div
161
+ aria-hidden
162
+ className={`absolute top-0 z-20 h-11 cursor-grab touch-none rounded-t-[18px] active:cursor-grabbing ${handleClassName}`}
163
+ onPointerDown={(event) => controls.start(event)}
164
+ />
165
+ </m.div>
166
+ </div>
167
+ );
168
+ }
@@ -0,0 +1,28 @@
1
+ // The tour's first stop: the dependency graph, the real one. Its toolbar,
2
+ // its two modes, its focus and export, on the example estate. Nothing here
3
+ // is a mock-up of the product; it is the product, in a smaller box.
4
+
5
+ import { useMemo, useState } from "react";
6
+ import { catalog } from "../data";
7
+ import { DependencyGraphPane } from "../graph/DependencyGraph";
8
+ import type { GraphMode } from "../graph/dependency-layout";
9
+ import { eventGraph } from "../lib/event-graph";
10
+ import { useSelectionCleared } from "./catalog";
11
+
12
+ export function EstateGraph() {
13
+ const graph = useMemo(() => eventGraph(catalog), []);
14
+ const [mode, setMode] = useState<GraphMode>("compact");
15
+ useSelectionCleared();
16
+
17
+ return (
18
+ <div className="h-[470px]">
19
+ <DependencyGraphPane
20
+ graph={graph}
21
+ mode={mode}
22
+ onMode={setMode}
23
+ fitKey={mode}
24
+ zoomOnScroll={false}
25
+ />
26
+ </div>
27
+ );
28
+ }