@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
@@ -0,0 +1,757 @@
1
+ import {
2
+ ArrowRight,
3
+ Bot,
4
+ Braces,
5
+ Check,
6
+ CircleDotDashed,
7
+ Code2,
8
+ Copy,
9
+ FileSearch,
10
+ Files,
11
+ FileText,
12
+ GitBranch,
13
+ Moon,
14
+ Network,
15
+ ShieldCheck,
16
+ Sun,
17
+ Terminal,
18
+ } from "lucide-react";
19
+ import { lazy, Suspense, useState } from "react";
20
+ import { Link } from "react-router";
21
+ import { CompassRose, Wordmark } from "../components/logo";
22
+ import { DiagramSkeleton } from "../components/DiagramSkeleton";
23
+ import { useTheme } from "../app/theme";
24
+ import { useDocumentTitle } from "../app/title";
25
+ import { catalog } from "../data";
26
+ import { m } from "../lib/motion";
27
+ import { paths } from "../routes";
28
+ import { catalogTo } from "./catalog";
29
+ import { DraggableReveal } from "./DraggableReveal";
30
+ import { heroColumn, heroLine, Reveal } from "./motion";
31
+ import { ProductFrame } from "./ProductFrame";
32
+ import { ProductTour } from "./ProductTour";
33
+
34
+ const REPOSITORY = "https://github.com/shortlink-org/portolan";
35
+ const EXAMPLE_DOCS = "https://shortlink-org.github.io/portolan/docs/example";
36
+ const exampleTo = catalogTo(paths.overview());
37
+
38
+ // The hero's map carries elk and React Flow. The words arrive first; the map
39
+ // follows a moment later into a frame that already has its height.
40
+ const HeroMap = lazy(() =>
41
+ import("./HeroMap").then((mod) => ({ default: mod.HeroMap })),
42
+ );
43
+
44
+ const EmbeddedChat =
45
+ import.meta.env.VITE_CHAT !== "off"
46
+ ? lazy(() =>
47
+ import("../chat/ChatPanel").then((mod) => ({
48
+ default: mod.ChatSurface,
49
+ })),
50
+ )
51
+ : null;
52
+
53
+ function Header() {
54
+ const { theme, toggle } = useTheme();
55
+
56
+ return (
57
+ <header className="sticky top-0 z-50 border-b border-line bg-canvas/85 backdrop-blur-xl">
58
+ <div className="mx-auto flex h-16 max-w-[1200px] items-center gap-5 px-5 sm:px-8">
59
+ <a href="#top" aria-label="Portolan home">
60
+ <Wordmark size={19} />
61
+ </a>
62
+ <nav
63
+ aria-label="Landing page"
64
+ className="ml-5 hidden items-center gap-5 text-sm text-muted md:flex"
65
+ >
66
+ <a className="hover:text-ink" href="#product">
67
+ Product
68
+ </a>
69
+ <a className="hover:text-ink" href="#how-it-works">
70
+ How it works
71
+ </a>
72
+ <a className="hover:text-ink" href="#ai-output">
73
+ For agents
74
+ </a>
75
+ <a className="hover:text-ink" href="#get-started">
76
+ Get started
77
+ </a>
78
+ </nav>
79
+ <div className="ml-auto flex items-center gap-2">
80
+ <button
81
+ type="button"
82
+ onClick={toggle}
83
+ className="tbtn size-8 justify-center p-0"
84
+ aria-label={`Use ${theme === "dark" ? "light" : "dark"} theme`}
85
+ >
86
+ {theme === "dark" ? <Sun size={15} /> : <Moon size={15} />}
87
+ </button>
88
+ <a
89
+ href={REPOSITORY}
90
+ className="tbtn hidden sm:flex"
91
+ target="_blank"
92
+ rel="noreferrer"
93
+ >
94
+ <GitBranch size={15} /> GitHub
95
+ </a>
96
+ <Link to={exampleTo} className="btn-accent landing-primary">
97
+ Explore example <ArrowRight size={14} />
98
+ </Link>
99
+ </div>
100
+ </div>
101
+ </header>
102
+ );
103
+ }
104
+
105
+ function HeroDemo() {
106
+ const services = catalog.contexts.reduce((n, c) => n + c.services.length, 0);
107
+ const stats: Array<[number, string]> = [
108
+ [catalog.contexts.length, "contexts"],
109
+ [services, "services"],
110
+ [catalog.flows.length, "flows"],
111
+ ];
112
+
113
+ return (
114
+ <ProductFrame
115
+ title="Context map · example estate"
116
+ eyebrow="estate"
117
+ className="landing-product-shadow"
118
+ aside={false}
119
+ >
120
+ <div className="flex flex-wrap items-center justify-between gap-3 border-b border-line bg-canvas px-4 py-3">
121
+ <div className="min-w-0">
122
+ <div className="font-semibold text-ink">Context map</div>
123
+ <div className="mono mt-0.5 truncate text-faint">
124
+ one line per relationship · select a domain · open its catalog
125
+ </div>
126
+ </div>
127
+ <div className="flex gap-2">
128
+ {stats.map(([value, label]) => (
129
+ <div
130
+ key={label}
131
+ className="rounded-control border border-line bg-canvas px-2 py-1"
132
+ >
133
+ <span className="tnum font-semibold text-ink">{value}</span>{" "}
134
+ <span className="mono text-faint">{label}</span>
135
+ </div>
136
+ ))}
137
+ </div>
138
+ </div>
139
+ <div className="relative h-[420px] sm:h-[440px] lg:h-[420px]">
140
+ <Suspense fallback={<DiagramSkeleton />}>
141
+ <HeroMap />
142
+ </Suspense>
143
+ </div>
144
+ </ProductFrame>
145
+ );
146
+ }
147
+
148
+ const inputGroups = [
149
+ { label: "code", items: ["Go", "TypeScript", "Java", "Rust", "Django"] },
150
+ { label: "contracts", items: ["OpenAPI", "GraphQL", "Protobuf"] },
151
+ { label: "runtime + data", items: ["SQL", "OTel"] },
152
+ ];
153
+
154
+ const features = [
155
+ {
156
+ icon: FileSearch,
157
+ title: "Source-level answers",
158
+ copy: "Open the exact file and line behind an event, field, call or flow step without leaving the architecture context.",
159
+ },
160
+ {
161
+ icon: ShieldCheck,
162
+ title: "Drift caught before merge",
163
+ copy: "Unresolved calls, mismatched schemas, second writers and missing publishers become explicit checks instead of tribal knowledge.",
164
+ },
165
+ {
166
+ icon: GitBranch,
167
+ title: "Made for pull requests",
168
+ copy: "Generated docs, diagrams and exports are ordinary files, while portolan check fails when committed architecture is stale.",
169
+ },
170
+ ];
171
+
172
+ const agentDocs = [
173
+ {
174
+ icon: FileText,
175
+ name: "llms.txt",
176
+ label: "Index for retrieval",
177
+ href: `${EXAMPLE_DOCS}/llms.txt`,
178
+ copy: "Contexts, services, aggregates, flows, glossaries and ADRs, linked to page-sized Markdown. An agent starts here and fetches only what the task needs.",
179
+ },
180
+ {
181
+ icon: Files,
182
+ name: "llms-full.txt",
183
+ label: "Complete context",
184
+ href: `${EXAMPLE_DOCS}/llms-full.txt`,
185
+ copy: "Every generated architecture page in one ordered file, for models that have room to reason across the whole estate at once.",
186
+ },
187
+ ];
188
+
189
+ const quickStartCommand = "npx @shortlink-org/portolan init";
190
+ const setupCommands = [
191
+ quickStartCommand,
192
+ "npm install --save-dev @shortlink-org/portolan",
193
+ "npx portolan generate",
194
+ "npx portolan dev",
195
+ ].join("\n");
196
+
197
+ function CopyCommand({
198
+ text,
199
+ label = "Copy",
200
+ className = "",
201
+ }: {
202
+ text: string;
203
+ label?: string;
204
+ className?: string;
205
+ }) {
206
+ const [copied, setCopied] = useState(false);
207
+
208
+ const copy = async () => {
209
+ let written = false;
210
+ if (navigator.clipboard) {
211
+ try {
212
+ await navigator.clipboard.writeText(text);
213
+ written = true;
214
+ } catch {
215
+ // Fall through to the selection-based path used by stricter browsers.
216
+ }
217
+ }
218
+ if (!written) {
219
+ const field = document.createElement("textarea");
220
+ field.value = text;
221
+ field.readOnly = true;
222
+ field.style.position = "fixed";
223
+ field.style.left = "-9999px";
224
+ document.body.appendChild(field);
225
+ field.select();
226
+ written = document.execCommand("copy");
227
+ field.remove();
228
+ }
229
+ if (written) {
230
+ setCopied(true);
231
+ window.setTimeout(() => setCopied(false), 1600);
232
+ }
233
+ };
234
+
235
+ return (
236
+ <button
237
+ type="button"
238
+ onClick={() => void copy()}
239
+ className={`tbtn shrink-0 ${className}`}
240
+ aria-label={`${label}: ${text}`}
241
+ >
242
+ {copied ? <Check size={14} /> : <Copy size={14} />}
243
+ {copied ? "Copied" : label}
244
+ </button>
245
+ );
246
+ }
247
+
248
+ const pipeline = [
249
+ {
250
+ icon: Code2,
251
+ label: "01 / read",
252
+ title: "Code and specifications",
253
+ copy: "Existing source, schemas, contracts, ADRs and traces.",
254
+ },
255
+ {
256
+ icon: Braces,
257
+ label: "02 / extract",
258
+ title: "Local fragments",
259
+ copy: "Each service publishes the architectural facts it owns.",
260
+ },
261
+ {
262
+ icon: CircleDotDashed,
263
+ label: "03 / validate",
264
+ title: "One estate model",
265
+ copy: "Portolan merges the union and checks every relationship.",
266
+ },
267
+ {
268
+ icon: Network,
269
+ label: "04 / publish",
270
+ title: "A static catalog",
271
+ copy: "Browse the site or export Markdown, Mermaid and Backstage entities.",
272
+ },
273
+ ];
274
+
275
+ export function LandingPage() {
276
+ useDocumentTitle("Architecture from code");
277
+
278
+ return (
279
+ <div className="h-full overflow-y-auto scroll-smooth bg-canvas text-ink">
280
+ <Header />
281
+ <main id="top">
282
+ <section className="landing-hero relative overflow-hidden border-b border-line">
283
+ <div className="landing-orbit landing-orbit-one" aria-hidden />
284
+ <div className="landing-orbit landing-orbit-two" aria-hidden />
285
+ <div className="landing-hero-grid relative mx-auto grid max-w-[1200px] items-center gap-14 px-5 py-20 sm:px-8 sm:py-24 lg:grid-cols-[minmax(0,0.92fr)_minmax(500px,1.08fr)] lg:py-20">
286
+ <m.div
287
+ className="max-w-[680px]"
288
+ variants={heroColumn}
289
+ initial="hidden"
290
+ animate="shown"
291
+ >
292
+ <m.div
293
+ variants={heroLine}
294
+ className="mono mb-6 flex items-center gap-2 text-accent"
295
+ >
296
+ <CompassRose size={15} />
297
+ ARCHITECTURE CATALOG
298
+ </m.div>
299
+ <m.h1
300
+ variants={heroLine}
301
+ className="landing-hero-title max-w-[760px] text-[clamp(3rem,6.4vw,5.8rem)] leading-[0.96] font-semibold tracking-[-0.055em] text-ink"
302
+ >
303
+ Your architecture, read from the code.
304
+ </m.h1>
305
+ <m.p
306
+ variants={heroLine}
307
+ className="mt-7 max-w-[620px] text-[17px] leading-7 text-muted sm:text-[19px] sm:leading-8"
308
+ >
309
+ Portolan turns code, specifications, schemas, traces and ADRs
310
+ into a validated, navigable map of your software estate.
311
+ </m.p>
312
+ <m.div
313
+ variants={heroLine}
314
+ className="mt-8 flex flex-wrap items-center gap-3"
315
+ >
316
+ <Link
317
+ to={exampleTo}
318
+ className="btn-accent landing-primary px-4 py-2.5"
319
+ >
320
+ Explore example catalog <ArrowRight size={15} />
321
+ </Link>
322
+ <a href="#get-started" className="tbtn px-4 py-2.5 text-ink">
323
+ <Terminal size={15} /> Get started
324
+ </a>
325
+ </m.div>
326
+ <m.div
327
+ variants={heroLine}
328
+ className="mono mt-6 flex flex-wrap items-center gap-x-5 gap-y-2 text-faint"
329
+ >
330
+ <span className="flex items-center gap-1.5">
331
+ <Check size={12} className="text-verified" /> no hosted
332
+ backend
333
+ </span>
334
+ <span className="flex items-center gap-1.5">
335
+ <Check size={12} className="text-verified" /> exact source
336
+ links
337
+ </span>
338
+ <a
339
+ href="#ai-output"
340
+ className="flex items-center gap-1.5 hover:text-accent"
341
+ >
342
+ <Check size={12} className="text-verified" /> llms.txt for
343
+ agents
344
+ </a>
345
+ </m.div>
346
+ </m.div>
347
+ <m.div
348
+ className="relative mx-auto w-full max-w-[650px] lg:translate-x-5"
349
+ initial={{ opacity: 0, y: 18 }}
350
+ animate={{ opacity: 1, y: 0 }}
351
+ transition={{ duration: 0.4, ease: [0.2, 0, 0, 1], delay: 0.25 }}
352
+ >
353
+ <DraggableReveal>
354
+ <HeroDemo />
355
+ </DraggableReveal>
356
+ </m.div>
357
+ </div>
358
+ </section>
359
+
360
+ <section aria-label="Built-in inputs" className="border-b border-line">
361
+ <div className="mx-auto flex max-w-[1200px] flex-col gap-5 px-5 py-7 sm:px-8 lg:flex-row lg:items-center">
362
+ <div className="shrink-0">
363
+ <div className="mono text-faint">BUILT-IN INPUTS</div>
364
+ <a
365
+ href={`${REPOSITORY}#plugins`}
366
+ target="_blank"
367
+ rel="noreferrer"
368
+ className="mt-1 inline-flex items-center gap-1 text-sm text-accent hover:underline"
369
+ >
370
+ See every extractor <ArrowRight size={12} />
371
+ </a>
372
+ </div>
373
+ <div className="flex flex-wrap gap-x-7 gap-y-3 lg:ml-auto lg:justify-end">
374
+ {inputGroups.map((group) => (
375
+ <div
376
+ key={group.label}
377
+ className="flex flex-wrap items-center gap-x-3 gap-y-1"
378
+ >
379
+ <span className="mono text-faint">{group.label}</span>
380
+ {group.items.map((input) => (
381
+ <span key={input} className="mono text-muted">
382
+ {input}
383
+ </span>
384
+ ))}
385
+ </div>
386
+ ))}
387
+ </div>
388
+ </div>
389
+ </section>
390
+
391
+ <section id="product" className="scroll-mt-20 border-b border-line">
392
+ <div className="mx-auto max-w-[1200px] px-5 py-20 sm:px-8 sm:py-28">
393
+ <Reveal className="mb-12 max-w-[720px]">
394
+ <div className="label text-accent">THE PRODUCT</div>
395
+ <h2 className="mt-4 text-[clamp(2rem,4vw,3.75rem)] leading-[1.04] font-semibold tracking-[-0.04em]">
396
+ Start wide. Follow the evidence all the way down.
397
+ </h2>
398
+ <p className="mt-5 max-w-[650px] text-[17px] leading-7 text-muted">
399
+ The catalog is not a poster. Every map, relationship and health
400
+ signal leads to the entity, contract and source that produced
401
+ it.
402
+ </p>
403
+ </Reveal>
404
+ <Reveal delay={0.08}>
405
+ <ProductTour />
406
+ </Reveal>
407
+ </div>
408
+ </section>
409
+
410
+ <section
411
+ id="how-it-works"
412
+ className="scroll-mt-20 border-b border-line bg-surface/40"
413
+ >
414
+ <div className="mx-auto max-w-[1200px] px-5 py-20 sm:px-8 sm:py-28">
415
+ <Reveal className="max-w-[760px]">
416
+ <div className="label text-accent">HOW IT WORKS</div>
417
+ <h2 className="mt-4 text-[clamp(2rem,4vw,3.75rem)] leading-[1.04] font-semibold tracking-[-0.04em]">
418
+ Architecture documentation that begins with evidence.
419
+ </h2>
420
+ </Reveal>
421
+ <div className="mt-12 grid gap-4 md:grid-cols-2 xl:grid-cols-4">
422
+ {pipeline.map((item, index) => {
423
+ const Icon = item.icon;
424
+ return (
425
+ <Reveal
426
+ key={item.title}
427
+ delay={index * 0.07}
428
+ className="relative rounded-card border border-line bg-canvas p-5 shadow-xs"
429
+ >
430
+ <div className="flex items-center justify-between">
431
+ <span className="flow-tile text-accent">
432
+ <Icon size={14} />
433
+ </span>
434
+ <span className="mono text-faint">{item.label}</span>
435
+ </div>
436
+ <h3 className="mt-8 text-md font-semibold">{item.title}</h3>
437
+ <p className="mt-2 text-sm text-muted">{item.copy}</p>
438
+ {index < pipeline.length - 1 ? (
439
+ <ArrowRight
440
+ size={16}
441
+ className="absolute top-1/2 -right-[11px] z-10 hidden -translate-y-1/2 rounded-full bg-canvas text-faint xl:block"
442
+ aria-hidden
443
+ />
444
+ ) : null}
445
+ </Reveal>
446
+ );
447
+ })}
448
+ </div>
449
+ </div>
450
+ </section>
451
+
452
+ <section className="border-b border-line">
453
+ <div className="mx-auto max-w-[1200px] px-5 py-20 sm:px-8 sm:py-28">
454
+ <Reveal className="grid items-end gap-5 md:grid-cols-2">
455
+ <div>
456
+ <div className="label text-accent">WHY PORTOLAN</div>
457
+ <h2 className="mt-4 max-w-[700px] text-[clamp(2rem,4vw,3.75rem)] leading-[1.04] font-semibold tracking-[-0.04em]">
458
+ Evidence that travels with the change.
459
+ </h2>
460
+ </div>
461
+ <p className="max-w-[520px] text-[17px] leading-7 text-muted md:justify-self-end">
462
+ Every view keeps its source, validation status and reviewable
463
+ artifact close enough to act on.
464
+ </p>
465
+ </Reveal>
466
+ <div className="mt-12 grid gap-4 md:grid-cols-3">
467
+ {features.map((feature, index) => {
468
+ const Icon = feature.icon;
469
+ return (
470
+ <Reveal
471
+ key={feature.title}
472
+ delay={(index % 3) * 0.07}
473
+ className="rounded-card border border-line p-5 transition-colors hover:border-line-strong hover:bg-surface"
474
+ >
475
+ <span className="flow-tile text-accent">
476
+ <Icon size={14} />
477
+ </span>
478
+ <h3 className="mt-7 text-md font-semibold">
479
+ {feature.title}
480
+ </h3>
481
+ <p className="mt-2 text-sm leading-6 text-muted">
482
+ {feature.copy}
483
+ </p>
484
+ </Reveal>
485
+ );
486
+ })}
487
+ </div>
488
+ </div>
489
+ </section>
490
+
491
+ <section
492
+ id="ai-output"
493
+ aria-label="Architecture output for coding agents"
494
+ className="relative overflow-hidden border-b border-line bg-surface/40"
495
+ >
496
+ <div
497
+ aria-hidden
498
+ className="pointer-events-none absolute top-[-240px] right-[-180px] size-[620px] rounded-full bg-accent/[0.045] blur-3xl"
499
+ />
500
+ <div className="relative mx-auto max-w-[1200px] px-5 py-20 sm:px-8 sm:py-24">
501
+ <Reveal className="grid gap-7 lg:grid-cols-[minmax(0,1.15fr)_minmax(380px,0.85fr)] lg:items-end">
502
+ <div className="max-w-[720px]">
503
+ <div className="label flex items-center gap-2 text-accent">
504
+ <Bot size={14} /> FOR CODING AGENTS
505
+ </div>
506
+ <h2 className="mt-4 text-[clamp(2rem,4vw,3.75rem)] leading-[1.04] font-semibold tracking-[-0.04em]">
507
+ Give agents the map before they change the territory.
508
+ </h2>
509
+ </div>
510
+ <div className="max-w-[520px] lg:justify-self-end">
511
+ <p className="text-[17px] leading-7 text-muted">
512
+ Portolan turns the same measured catalog people browse into
513
+ model-readable context. The built-in chat answers with links,
514
+ cards and diagrams — not a second AI summary that can drift.
515
+ </p>
516
+ <div className="mono mt-5 flex flex-wrap gap-x-5 gap-y-2 text-faint">
517
+ <span className="flex items-center gap-1.5">
518
+ <Check size={12} className="text-verified" /> same catalog
519
+ </span>
520
+ <span className="flex items-center gap-1.5">
521
+ <Check size={12} className="text-verified" /> commit stamped
522
+ </span>
523
+ <span className="flex items-center gap-1.5">
524
+ <Check size={12} className="text-verified" /> source linked
525
+ </span>
526
+ </div>
527
+ <a
528
+ href="#catalog-chat"
529
+ className="mt-6 inline-flex items-center gap-1.5 text-sm font-medium text-accent hover:underline"
530
+ >
531
+ Try the live chat{" "}
532
+ <ArrowRight size={13} className="rotate-90" />
533
+ </a>
534
+ </div>
535
+ </Reveal>
536
+
537
+ <div className="mt-12 grid min-w-0 gap-4 lg:grid-cols-[300px_minmax(0,1fr)]">
538
+ <Reveal className="flex min-w-0 flex-col rounded-card border border-line bg-canvas p-4 shadow-xs sm:p-5">
539
+ <div className="flex items-center justify-between gap-3 border-b border-line pb-4">
540
+ <div>
541
+ <div className="label text-accent">MODEL CONTEXT</div>
542
+ <div className="mono mt-1 text-faint">
543
+ generated together
544
+ </div>
545
+ </div>
546
+ <span className="flow-tile text-accent">
547
+ <Files size={14} />
548
+ </span>
549
+ </div>
550
+
551
+ <div className="mt-4 space-y-3">
552
+ {agentDocs.map((doc) => {
553
+ const Icon = doc.icon;
554
+ return (
555
+ <a
556
+ key={doc.name}
557
+ href={doc.href}
558
+ target="_blank"
559
+ rel="noreferrer"
560
+ className="group block rounded-card border border-line bg-surface/55 p-4 transition-colors hover:border-line-strong hover:bg-raised"
561
+ >
562
+ <div className="flex items-start gap-3">
563
+ <Icon
564
+ size={15}
565
+ className="mt-0.5 shrink-0 text-accent"
566
+ />
567
+ <div className="min-w-0 flex-1">
568
+ <div className="flex flex-wrap items-center justify-between gap-2">
569
+ <h3 className="mono font-semibold text-ink">
570
+ {doc.name}
571
+ </h3>
572
+ <ArrowRight
573
+ size={12}
574
+ className="text-faint transition-transform group-hover:translate-x-0.5 group-hover:text-accent"
575
+ />
576
+ </div>
577
+ <div className="mono mt-1 text-faint">
578
+ {doc.label}
579
+ </div>
580
+ </div>
581
+ </div>
582
+ <p className="mt-3 text-sm leading-6 text-muted">
583
+ {doc.copy}
584
+ </p>
585
+ </a>
586
+ );
587
+ })}
588
+ </div>
589
+
590
+ <div className="mono mt-auto flex items-start gap-2 border-t border-line pt-4 text-faint">
591
+ <Bot size={13} className="mt-0.5 shrink-0 text-accent" />
592
+ <span>choose the index or load the whole estate</span>
593
+ </div>
594
+ </Reveal>
595
+
596
+ {EmbeddedChat ? (
597
+ <div id="catalog-chat" className="min-w-0 scroll-mt-20">
598
+ <Reveal delay={0.08} className="min-w-0">
599
+ <DraggableReveal label="Movable catalog chat">
600
+ <ProductFrame
601
+ title="Ask the catalog · portolan"
602
+ eyebrow="agents"
603
+ aside={false}
604
+ >
605
+ <div className="h-[605px] overflow-hidden bg-canvas">
606
+ <Suspense
607
+ fallback={
608
+ <div className="mono flex h-full items-center justify-center text-muted">
609
+ loading the catalog chat…
610
+ </div>
611
+ }
612
+ >
613
+ <EmbeddedChat embedded />
614
+ </Suspense>
615
+ </div>
616
+ </ProductFrame>
617
+ </DraggableReveal>
618
+ </Reveal>
619
+ </div>
620
+ ) : null}
621
+ </div>
622
+ </div>
623
+ </section>
624
+
625
+ <section id="get-started" className="scroll-mt-20 border-b border-line">
626
+ <div className="mx-auto grid max-w-[1200px] grid-cols-1 items-center gap-12 px-5 py-20 sm:px-8 sm:py-28 lg:grid-cols-[minmax(0,0.9fr)_minmax(440px,1.1fr)]">
627
+ <Reveal className="min-w-0">
628
+ <div className="label text-accent">GET STARTED</div>
629
+ <h2 className="mt-4 text-[clamp(2rem,4vw,3.75rem)] leading-[1.04] font-semibold tracking-[-0.04em]">
630
+ Start with one command.
631
+ </h2>
632
+ <p className="mt-5 max-w-[580px] text-[17px] leading-7 text-muted">
633
+ Run the initializer at a repository root. It detects projects,
634
+ specifications and toolchains, then proposes the manifest.
635
+ </p>
636
+ <div className="mt-7 flex min-w-0 items-center gap-3 rounded-card border border-line bg-surface p-2 pl-3 shadow-xs">
637
+ <Terminal size={15} className="shrink-0 text-accent" />
638
+ <code className="mono min-w-0 flex-1 truncate text-sm text-ink">
639
+ {quickStartCommand}
640
+ </code>
641
+ <CopyCommand text={quickStartCommand} label="Copy" />
642
+ </div>
643
+ <div className="mt-4 flex flex-wrap gap-3">
644
+ <Link
645
+ to={exampleTo}
646
+ className="btn-accent landing-primary px-4 py-2.5"
647
+ >
648
+ Open the example <ArrowRight size={15} />
649
+ </Link>
650
+ <a
651
+ href={`${REPOSITORY}#use-it-in-your-project`}
652
+ className="tbtn px-4 py-2.5 text-ink"
653
+ target="_blank"
654
+ rel="noreferrer"
655
+ >
656
+ Read the guide
657
+ </a>
658
+ </div>
659
+ </Reveal>
660
+ <Reveal delay={0.1} className="min-w-0">
661
+ <DraggableReveal
662
+ handleClassName="right-24 left-0"
663
+ label="Movable setup terminal"
664
+ >
665
+ <div className="min-w-0 overflow-hidden rounded-[14px] border border-line bg-[#090c10] shadow-md">
666
+ <div className="flex h-11 items-center gap-1.5 border-b border-white/10 px-4">
667
+ <span className="size-2 rounded-full bg-white/15" />
668
+ <span className="size-2 rounded-full bg-white/15" />
669
+ <span className="size-2 rounded-full bg-white/15" />
670
+ <span className="mono ml-auto text-white/35">
671
+ next steps
672
+ </span>
673
+ <CopyCommand
674
+ text={setupCommands}
675
+ label="Copy all"
676
+ className="h-7 border-white/10 bg-white/5 px-2 text-white/60 hover:bg-white/10 hover:text-white"
677
+ />
678
+ </div>
679
+ <pre className="overflow-x-auto p-5 text-[13px] leading-7 text-white/75 sm:p-7">
680
+ <code>
681
+ <span className="text-white/35">$</span> npx
682
+ @shortlink-org/portolan init{"\n"}
683
+ <span className="text-[#72d5c4]">✓</span> projects and
684
+ specifications detected{"\n"}
685
+ <span className="text-[#72d5c4]">✓</span> portolan.json
686
+ ready{"\n\n"}
687
+ <span className="text-white/35">$</span> npm install
688
+ --save-dev @shortlink-org/portolan{"\n"}
689
+ <span className="text-white/35">$</span> npx portolan
690
+ generate{"\n"}
691
+ <span className="text-[#72d5c4]">✓</span> catalog merged
692
+ and validated{"\n\n"}
693
+ <span className="text-white/35">$</span> npx portolan dev
694
+ {"\n"}
695
+ <span className="text-[#72d5c4]">✓</span> site ready at
696
+ http://localhost:5173
697
+ </code>
698
+ </pre>
699
+ </div>
700
+ </DraggableReveal>
701
+ </Reveal>
702
+ </div>
703
+ </section>
704
+
705
+ <section className="landing-final relative overflow-hidden">
706
+ <Reveal className="mx-auto max-w-[920px] px-5 py-24 text-center sm:px-8 sm:py-32">
707
+ <CompassRose size={28} className="mx-auto text-accent" />
708
+ <h2 className="mt-7 text-[clamp(2.4rem,5vw,4.8rem)] leading-[1] font-semibold tracking-[-0.05em]">
709
+ Make the architecture explorable.
710
+ </h2>
711
+ <p className="mx-auto mt-5 max-w-[600px] text-[17px] leading-7 text-muted">
712
+ Give every diagram, flow and dependency a path back to the code
713
+ that made it true.
714
+ </p>
715
+ <div className="mt-8 flex flex-wrap justify-center gap-3">
716
+ <Link
717
+ to={exampleTo}
718
+ className="btn-accent landing-primary px-4 py-2.5"
719
+ >
720
+ Explore example catalog <ArrowRight size={15} />
721
+ </Link>
722
+ <a
723
+ href={REPOSITORY}
724
+ target="_blank"
725
+ rel="noreferrer"
726
+ className="tbtn px-4 py-2.5 text-ink"
727
+ >
728
+ <GitBranch size={15} /> View on GitHub
729
+ </a>
730
+ </div>
731
+ </Reveal>
732
+ </section>
733
+ </main>
734
+
735
+ <footer className="border-t border-line">
736
+ <div className="mx-auto flex max-w-[1200px] flex-col gap-4 px-5 py-7 text-sm text-muted sm:flex-row sm:items-center sm:px-8">
737
+ <Wordmark />
738
+ <span className="sm:ml-3">Architecture from code and evidence.</span>
739
+ <div className="flex gap-5 sm:ml-auto">
740
+ <a className="hover:text-ink" href={`${REPOSITORY}#readme`}>
741
+ Docs
742
+ </a>
743
+ <a
744
+ className="hover:text-ink"
745
+ href={`${REPOSITORY}/blob/main/LICENSE`}
746
+ >
747
+ MIT License
748
+ </a>
749
+ <a className="hover:text-ink" href={REPOSITORY}>
750
+ GitHub
751
+ </a>
752
+ </div>
753
+ </div>
754
+ </footer>
755
+ </div>
756
+ );
757
+ }