quiver-cli 1.3.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (407) hide show
  1. package/README.md +197 -239
  2. package/dist/cli.js +3163 -2040
  3. package/package.json +10 -11
  4. package/template/.agents/AGENTS.md +0 -26
  5. package/template/.agents/commands/cp.md +0 -108
  6. package/template/.agents/commands/next-setup.md +0 -1064
  7. package/template/.agents/commands/review.md +0 -68
  8. package/template/.agents/commands/terraform-readme.md +0 -38
  9. package/template/.agents/config.json +0 -80
  10. package/template/.agents/plugins/opencode/rtk.ts +0 -34
  11. package/template/.agents/skills/agent-browser/SKILL.md +0 -52
  12. package/template/.agents/skills/apps/skybridge/SKILL.md +0 -52
  13. package/template/.agents/skills/code/cleanup/SKILL.md +0 -26
  14. package/template/.agents/skills/code/improve/SKILL.md +0 -122
  15. package/template/.agents/skills/code/improve/references/audit-playbook.md +0 -130
  16. package/template/.agents/skills/code/improve/references/closing-the-loop.md +0 -96
  17. package/template/.agents/skills/code/improve/references/plan-template.md +0 -197
  18. package/template/.agents/skills/code/vercel-react-best-practices/AGENTS.md +0 -3810
  19. package/template/.agents/skills/code/vercel-react-best-practices/README.md +0 -123
  20. package/template/.agents/skills/code/vercel-react-best-practices/SKILL.md +0 -149
  21. package/template/.agents/skills/code/vercel-react-best-practices/metadata.json +0 -15
  22. package/template/.agents/skills/code/vercel-react-best-practices/rules/_sections.md +0 -46
  23. package/template/.agents/skills/code/vercel-react-best-practices/rules/_template.md +0 -28
  24. package/template/.agents/skills/code/vercel-react-best-practices/rules/advanced-effect-event-deps.md +0 -56
  25. package/template/.agents/skills/code/vercel-react-best-practices/rules/advanced-event-handler-refs.md +0 -55
  26. package/template/.agents/skills/code/vercel-react-best-practices/rules/advanced-init-once.md +0 -42
  27. package/template/.agents/skills/code/vercel-react-best-practices/rules/advanced-use-latest.md +0 -39
  28. package/template/.agents/skills/code/vercel-react-best-practices/rules/async-api-routes.md +0 -38
  29. package/template/.agents/skills/code/vercel-react-best-practices/rules/async-cheap-condition-before-await.md +0 -37
  30. package/template/.agents/skills/code/vercel-react-best-practices/rules/async-defer-await.md +0 -82
  31. package/template/.agents/skills/code/vercel-react-best-practices/rules/async-dependencies.md +0 -51
  32. package/template/.agents/skills/code/vercel-react-best-practices/rules/async-parallel.md +0 -28
  33. package/template/.agents/skills/code/vercel-react-best-practices/rules/async-suspense-boundaries.md +0 -99
  34. package/template/.agents/skills/code/vercel-react-best-practices/rules/bundle-analyzable-paths.md +0 -63
  35. package/template/.agents/skills/code/vercel-react-best-practices/rules/bundle-barrel-imports.md +0 -60
  36. package/template/.agents/skills/code/vercel-react-best-practices/rules/bundle-conditional.md +0 -31
  37. package/template/.agents/skills/code/vercel-react-best-practices/rules/bundle-defer-third-party.md +0 -49
  38. package/template/.agents/skills/code/vercel-react-best-practices/rules/bundle-dynamic-imports.md +0 -35
  39. package/template/.agents/skills/code/vercel-react-best-practices/rules/bundle-preload.md +0 -50
  40. package/template/.agents/skills/code/vercel-react-best-practices/rules/client-event-listeners.md +0 -74
  41. package/template/.agents/skills/code/vercel-react-best-practices/rules/client-localstorage-schema.md +0 -71
  42. package/template/.agents/skills/code/vercel-react-best-practices/rules/client-passive-event-listeners.md +0 -48
  43. package/template/.agents/skills/code/vercel-react-best-practices/rules/client-swr-dedup.md +0 -56
  44. package/template/.agents/skills/code/vercel-react-best-practices/rules/js-batch-dom-css.md +0 -107
  45. package/template/.agents/skills/code/vercel-react-best-practices/rules/js-cache-function-results.md +0 -80
  46. package/template/.agents/skills/code/vercel-react-best-practices/rules/js-cache-property-access.md +0 -28
  47. package/template/.agents/skills/code/vercel-react-best-practices/rules/js-cache-storage.md +0 -70
  48. package/template/.agents/skills/code/vercel-react-best-practices/rules/js-combine-iterations.md +0 -32
  49. package/template/.agents/skills/code/vercel-react-best-practices/rules/js-early-exit.md +0 -50
  50. package/template/.agents/skills/code/vercel-react-best-practices/rules/js-flatmap-filter.md +0 -60
  51. package/template/.agents/skills/code/vercel-react-best-practices/rules/js-hoist-regexp.md +0 -45
  52. package/template/.agents/skills/code/vercel-react-best-practices/rules/js-index-maps.md +0 -37
  53. package/template/.agents/skills/code/vercel-react-best-practices/rules/js-length-check-first.md +0 -49
  54. package/template/.agents/skills/code/vercel-react-best-practices/rules/js-min-max-loop.md +0 -82
  55. package/template/.agents/skills/code/vercel-react-best-practices/rules/js-request-idle-callback.md +0 -105
  56. package/template/.agents/skills/code/vercel-react-best-practices/rules/js-set-map-lookups.md +0 -24
  57. package/template/.agents/skills/code/vercel-react-best-practices/rules/js-tosorted-immutable.md +0 -57
  58. package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-activity.md +0 -26
  59. package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +0 -47
  60. package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-conditional-render.md +0 -40
  61. package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-content-visibility.md +0 -38
  62. package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-hoist-jsx.md +0 -46
  63. package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +0 -82
  64. package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +0 -30
  65. package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-resource-hints.md +0 -85
  66. package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-script-defer-async.md +0 -68
  67. package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-svg-precision.md +0 -28
  68. package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-usetransition-loading.md +0 -75
  69. package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-defer-reads.md +0 -39
  70. package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-dependencies.md +0 -45
  71. package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +0 -40
  72. package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-derived-state.md +0 -29
  73. package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-functional-setstate.md +0 -74
  74. package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-lazy-state-init.md +0 -58
  75. package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +0 -38
  76. package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-memo.md +0 -44
  77. package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +0 -45
  78. package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-no-inline-components.md +0 -82
  79. package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +0 -35
  80. package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-split-combined-hooks.md +0 -64
  81. package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-transitions.md +0 -40
  82. package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-use-deferred-value.md +0 -59
  83. package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +0 -73
  84. package/template/.agents/skills/code/vercel-react-best-practices/rules/server-after-nonblocking.md +0 -73
  85. package/template/.agents/skills/code/vercel-react-best-practices/rules/server-auth-actions.md +0 -96
  86. package/template/.agents/skills/code/vercel-react-best-practices/rules/server-cache-lru.md +0 -41
  87. package/template/.agents/skills/code/vercel-react-best-practices/rules/server-cache-react.md +0 -76
  88. package/template/.agents/skills/code/vercel-react-best-practices/rules/server-dedup-props.md +0 -65
  89. package/template/.agents/skills/code/vercel-react-best-practices/rules/server-hoist-static-io.md +0 -149
  90. package/template/.agents/skills/code/vercel-react-best-practices/rules/server-no-shared-module-state.md +0 -50
  91. package/template/.agents/skills/code/vercel-react-best-practices/rules/server-parallel-fetching.md +0 -83
  92. package/template/.agents/skills/code/vercel-react-best-practices/rules/server-parallel-nested-fetching.md +0 -34
  93. package/template/.agents/skills/code/vercel-react-best-practices/rules/server-serialization.md +0 -38
  94. package/template/.agents/skills/data/prisma-cli/SKILL.md +0 -265
  95. package/template/.agents/skills/data/prisma-cli/references/agent-safety.md +0 -27
  96. package/template/.agents/skills/data/prisma-cli/references/complete.md +0 -22
  97. package/template/.agents/skills/data/prisma-cli/references/db-execute.md +0 -78
  98. package/template/.agents/skills/data/prisma-cli/references/db-pull.md +0 -185
  99. package/template/.agents/skills/data/prisma-cli/references/db-push.md +0 -150
  100. package/template/.agents/skills/data/prisma-cli/references/db-seed.md +0 -188
  101. package/template/.agents/skills/data/prisma-cli/references/debug.md +0 -46
  102. package/template/.agents/skills/data/prisma-cli/references/dev.md +0 -157
  103. package/template/.agents/skills/data/prisma-cli/references/format.md +0 -48
  104. package/template/.agents/skills/data/prisma-cli/references/generate.md +0 -173
  105. package/template/.agents/skills/data/prisma-cli/references/init.md +0 -139
  106. package/template/.agents/skills/data/prisma-cli/references/mcp.md +0 -39
  107. package/template/.agents/skills/data/prisma-cli/references/migrate-deploy.md +0 -127
  108. package/template/.agents/skills/data/prisma-cli/references/migrate-dev.md +0 -145
  109. package/template/.agents/skills/data/prisma-cli/references/migrate-diff.md +0 -89
  110. package/template/.agents/skills/data/prisma-cli/references/migrate-reset.md +0 -80
  111. package/template/.agents/skills/data/prisma-cli/references/migrate-resolve.md +0 -57
  112. package/template/.agents/skills/data/prisma-cli/references/migrate-status.md +0 -65
  113. package/template/.agents/skills/data/prisma-cli/references/studio.md +0 -137
  114. package/template/.agents/skills/data/prisma-cli/references/validate.md +0 -53
  115. package/template/.agents/skills/data/prisma-client-api/SKILL.md +0 -216
  116. package/template/.agents/skills/data/prisma-client-api/references/client-methods.md +0 -223
  117. package/template/.agents/skills/data/prisma-client-api/references/constructor.md +0 -221
  118. package/template/.agents/skills/data/prisma-client-api/references/filters.md +0 -256
  119. package/template/.agents/skills/data/prisma-client-api/references/model-queries.md +0 -281
  120. package/template/.agents/skills/data/prisma-client-api/references/query-options.md +0 -276
  121. package/template/.agents/skills/data/prisma-client-api/references/raw-queries.md +0 -198
  122. package/template/.agents/skills/data/prisma-client-api/references/relations.md +0 -308
  123. package/template/.agents/skills/data/prisma-client-api/references/transactions.md +0 -184
  124. package/template/.agents/skills/design/impeccable/SKILL.md +0 -84
  125. package/template/.agents/skills/design/impeccable/reference/adapt.md +0 -312
  126. package/template/.agents/skills/design/impeccable/reference/adapt.native.md +0 -58
  127. package/template/.agents/skills/design/impeccable/reference/android.md +0 -46
  128. package/template/.agents/skills/design/impeccable/reference/animate.md +0 -89
  129. package/template/.agents/skills/design/impeccable/reference/audit.md +0 -136
  130. package/template/.agents/skills/design/impeccable/reference/audit.native.md +0 -139
  131. package/template/.agents/skills/design/impeccable/reference/bolder.md +0 -33
  132. package/template/.agents/skills/design/impeccable/reference/clarify.md +0 -94
  133. package/template/.agents/skills/design/impeccable/reference/colorize.md +0 -86
  134. package/template/.agents/skills/design/impeccable/reference/craft-floor.md +0 -44
  135. package/template/.agents/skills/design/impeccable/reference/craft.md +0 -5
  136. package/template/.agents/skills/design/impeccable/reference/critique.md +0 -806
  137. package/template/.agents/skills/design/impeccable/reference/degraded/asset-producer.md +0 -37
  138. package/template/.agents/skills/design/impeccable/reference/degraded/documenter.md +0 -24
  139. package/template/.agents/skills/design/impeccable/reference/degraded/finish-reviewer.md +0 -38
  140. package/template/.agents/skills/design/impeccable/reference/degraded/manual-edit-applier.md +0 -92
  141. package/template/.agents/skills/design/impeccable/reference/delight.md +0 -70
  142. package/template/.agents/skills/design/impeccable/reference/distill.md +0 -111
  143. package/template/.agents/skills/design/impeccable/reference/doctor.md +0 -54
  144. package/template/.agents/skills/design/impeccable/reference/document.md +0 -416
  145. package/template/.agents/skills/design/impeccable/reference/extract.md +0 -69
  146. package/template/.agents/skills/design/impeccable/reference/harden.md +0 -336
  147. package/template/.agents/skills/design/impeccable/reference/hooks.md +0 -111
  148. package/template/.agents/skills/design/impeccable/reference/init.md +0 -131
  149. package/template/.agents/skills/design/impeccable/reference/ios.md +0 -51
  150. package/template/.agents/skills/design/impeccable/reference/layout.md +0 -84
  151. package/template/.agents/skills/design/impeccable/reference/live-setup.md +0 -102
  152. package/template/.agents/skills/design/impeccable/reference/live.md +0 -323
  153. package/template/.agents/skills/design/impeccable/reference/new-work.md +0 -145
  154. package/template/.agents/skills/design/impeccable/reference/onboard.md +0 -234
  155. package/template/.agents/skills/design/impeccable/reference/operate.md +0 -61
  156. package/template/.agents/skills/design/impeccable/reference/optimize.md +0 -258
  157. package/template/.agents/skills/design/impeccable/reference/overdrive.md +0 -127
  158. package/template/.agents/skills/design/impeccable/reference/polish.md +0 -105
  159. package/template/.agents/skills/design/impeccable/reference/quieter.md +0 -99
  160. package/template/.agents/skills/design/impeccable/reference/routing.md +0 -18
  161. package/template/.agents/skills/design/impeccable/reference/shape.md +0 -59
  162. package/template/.agents/skills/design/impeccable/reference/typeset.md +0 -80
  163. package/template/.agents/skills/design/impeccable/reference/visualize.md +0 -46
  164. package/template/.agents/skills/design/impeccable/scripts/build-phase.mjs +0 -1022
  165. package/template/.agents/skills/design/impeccable/scripts/command-metadata.json +0 -94
  166. package/template/.agents/skills/design/impeccable/scripts/comp-diff.mjs +0 -391
  167. package/template/.agents/skills/design/impeccable/scripts/comp-spec.mjs +0 -513
  168. package/template/.agents/skills/design/impeccable/scripts/concept-seed.mjs +0 -814
  169. package/template/.agents/skills/design/impeccable/scripts/context-signals.mjs +0 -325
  170. package/template/.agents/skills/design/impeccable/scripts/context.mjs +0 -1565
  171. package/template/.agents/skills/design/impeccable/scripts/critique-storage.mjs +0 -473
  172. package/template/.agents/skills/design/impeccable/scripts/data/font-index-failures.json +0 -121
  173. package/template/.agents/skills/design/impeccable/scripts/data/font-index.json +0 -1
  174. package/template/.agents/skills/design/impeccable/scripts/detect-csp.mjs +0 -198
  175. package/template/.agents/skills/design/impeccable/scripts/detect.mjs +0 -30
  176. package/template/.agents/skills/design/impeccable/scripts/detector/browser/injected/index.mjs +0 -2204
  177. package/template/.agents/skills/design/impeccable/scripts/detector/cli/main.mjs +0 -432
  178. package/template/.agents/skills/design/impeccable/scripts/detector/design-system.mjs +0 -1311
  179. package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns-browser.js +0 -9043
  180. package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns.mjs +0 -51
  181. package/template/.agents/skills/design/impeccable/scripts/detector/engines/browser/detect-url.mjs +0 -434
  182. package/template/.agents/skills/design/impeccable/scripts/detector/engines/regex/detect-text.mjs +0 -1319
  183. package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +0 -1241
  184. package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/detect-html.mjs +0 -288
  185. package/template/.agents/skills/design/impeccable/scripts/detector/engines/visual/screenshot-contrast.mjs +0 -189
  186. package/template/.agents/skills/design/impeccable/scripts/detector/findings.mjs +0 -18
  187. package/template/.agents/skills/design/impeccable/scripts/detector/node/file-system.mjs +0 -213
  188. package/template/.agents/skills/design/impeccable/scripts/detector/profile/profiler.mjs +0 -166
  189. package/template/.agents/skills/design/impeccable/scripts/detector/registry/antipatterns.mjs +0 -635
  190. package/template/.agents/skills/design/impeccable/scripts/detector/rules/checks.mjs +0 -5681
  191. package/template/.agents/skills/design/impeccable/scripts/detector/shared/color.mjs +0 -596
  192. package/template/.agents/skills/design/impeccable/scripts/detector/shared/constants.mjs +0 -127
  193. package/template/.agents/skills/design/impeccable/scripts/detector/shared/fonts.mjs +0 -30
  194. package/template/.agents/skills/design/impeccable/scripts/detector/shared/inline-ignores.mjs +0 -148
  195. package/template/.agents/skills/design/impeccable/scripts/detector/shared/page.mjs +0 -7
  196. package/template/.agents/skills/design/impeccable/scripts/doctor.mjs +0 -329
  197. package/template/.agents/skills/design/impeccable/scripts/embed-prompt.mjs +0 -166
  198. package/template/.agents/skills/design/impeccable/scripts/font-match.mjs +0 -457
  199. package/template/.agents/skills/design/impeccable/scripts/generate-image.mjs +0 -447
  200. package/template/.agents/skills/design/impeccable/scripts/hook-admin.mjs +0 -819
  201. package/template/.agents/skills/design/impeccable/scripts/hook-before-edit.mjs +0 -538
  202. package/template/.agents/skills/design/impeccable/scripts/hook-lib.mjs +0 -2490
  203. package/template/.agents/skills/design/impeccable/scripts/hook.mjs +0 -79
  204. package/template/.agents/skills/design/impeccable/scripts/lib/artifact-schema.mjs +0 -93
  205. package/template/.agents/skills/design/impeccable/scripts/lib/composition-catalog.mjs +0 -200
  206. package/template/.agents/skills/design/impeccable/scripts/lib/concept-catalog.mjs +0 -396
  207. package/template/.agents/skills/design/impeccable/scripts/lib/design-parser.mjs +0 -880
  208. package/template/.agents/skills/design/impeccable/scripts/lib/font-fingerprint.mjs +0 -564
  209. package/template/.agents/skills/design/impeccable/scripts/lib/font-index.mjs +0 -130
  210. package/template/.agents/skills/design/impeccable/scripts/lib/hero-checks.mjs +0 -246
  211. package/template/.agents/skills/design/impeccable/scripts/lib/image-metrics.mjs +0 -306
  212. package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-config.mjs +0 -640
  213. package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-paths.mjs +0 -137
  214. package/template/.agents/skills/design/impeccable/scripts/lib/is-generated.mjs +0 -72
  215. package/template/.agents/skills/design/impeccable/scripts/lib/live-path-globs.mjs +0 -37
  216. package/template/.agents/skills/design/impeccable/scripts/lib/open-system-browser.mjs +0 -26
  217. package/template/.agents/skills/design/impeccable/scripts/lib/png.mjs +0 -281
  218. package/template/.agents/skills/design/impeccable/scripts/lib/provider.mjs +0 -5
  219. package/template/.agents/skills/design/impeccable/scripts/lib/raster.mjs +0 -194
  220. package/template/.agents/skills/design/impeccable/scripts/lib/roll-selection.mjs +0 -369
  221. package/template/.agents/skills/design/impeccable/scripts/lib/staleness-deep.mjs +0 -485
  222. package/template/.agents/skills/design/impeccable/scripts/lib/staleness-notice.mjs +0 -169
  223. package/template/.agents/skills/design/impeccable/scripts/lib/staleness.mjs +0 -533
  224. package/template/.agents/skills/design/impeccable/scripts/lib/surface-briefs.mjs +0 -149
  225. package/template/.agents/skills/design/impeccable/scripts/lib/target-args.mjs +0 -42
  226. package/template/.agents/skills/design/impeccable/scripts/lib/target-slug.mjs +0 -33
  227. package/template/.agents/skills/design/impeccable/scripts/lib/template-extensions.mjs +0 -146
  228. package/template/.agents/skills/design/impeccable/scripts/live/accept-css.mjs +0 -617
  229. package/template/.agents/skills/design/impeccable/scripts/live/accept-verify.mjs +0 -60
  230. package/template/.agents/skills/design/impeccable/scripts/live/browser-script-parts.mjs +0 -84
  231. package/template/.agents/skills/design/impeccable/scripts/live/completion.mjs +0 -28
  232. package/template/.agents/skills/design/impeccable/scripts/live/event-validation.mjs +0 -199
  233. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/astro.mjs +0 -47
  234. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/detect-utils.mjs +0 -73
  235. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/index.mjs +0 -143
  236. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/journal.mjs +0 -197
  237. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/nextjs.mjs +0 -49
  238. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/nuxt.mjs +0 -161
  239. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/script-src.mjs +0 -17
  240. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/static-html.mjs +0 -26
  241. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/sveltekit.mjs +0 -71
  242. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/tag-strategy.mjs +0 -247
  243. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/tanstack-start.mjs +0 -70
  244. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/vite-generic.mjs +0 -42
  245. package/template/.agents/skills/design/impeccable/scripts/live/generation-preflight.mjs +0 -149
  246. package/template/.agents/skills/design/impeccable/scripts/live/insert-ui.mjs +0 -458
  247. package/template/.agents/skills/design/impeccable/scripts/live/instructions.mjs +0 -142
  248. package/template/.agents/skills/design/impeccable/scripts/live/manual-apply.mjs +0 -939
  249. package/template/.agents/skills/design/impeccable/scripts/live/manual-edit-routes.mjs +0 -357
  250. package/template/.agents/skills/design/impeccable/scripts/live/manual-edits-buffer.mjs +0 -152
  251. package/template/.agents/skills/design/impeccable/scripts/live/poll-lanes.mjs +0 -14
  252. package/template/.agents/skills/design/impeccable/scripts/live/project-ignores.mjs +0 -139
  253. package/template/.agents/skills/design/impeccable/scripts/live/roots.mjs +0 -508
  254. package/template/.agents/skills/design/impeccable/scripts/live/session-store.mjs +0 -563
  255. package/template/.agents/skills/design/impeccable/scripts/live/source-lock.mjs +0 -105
  256. package/template/.agents/skills/design/impeccable/scripts/live/source-search.mjs +0 -105
  257. package/template/.agents/skills/design/impeccable/scripts/live/svelte-ast.mjs +0 -969
  258. package/template/.agents/skills/design/impeccable/scripts/live/svelte-component.mjs +0 -1366
  259. package/template/.agents/skills/design/impeccable/scripts/live/sveltekit-adapter.mjs +0 -304
  260. package/template/.agents/skills/design/impeccable/scripts/live/tanstack-adapter.mjs +0 -259
  261. package/template/.agents/skills/design/impeccable/scripts/live/ui-surfaces.mjs +0 -75
  262. package/template/.agents/skills/design/impeccable/scripts/live/vocabulary.mjs +0 -171
  263. package/template/.agents/skills/design/impeccable/scripts/live-accept.mjs +0 -938
  264. package/template/.agents/skills/design/impeccable/scripts/live-browser-dom.js +0 -146
  265. package/template/.agents/skills/design/impeccable/scripts/live-browser-ignores.js +0 -242
  266. package/template/.agents/skills/design/impeccable/scripts/live-browser-session.js +0 -123
  267. package/template/.agents/skills/design/impeccable/scripts/live-browser.js +0 -12551
  268. package/template/.agents/skills/design/impeccable/scripts/live-commit-manual-edits.mjs +0 -1200
  269. package/template/.agents/skills/design/impeccable/scripts/live-complete.mjs +0 -107
  270. package/template/.agents/skills/design/impeccable/scripts/live-copy-edit-agent.mjs +0 -800
  271. package/template/.agents/skills/design/impeccable/scripts/live-discard-manual-edits.mjs +0 -51
  272. package/template/.agents/skills/design/impeccable/scripts/live-inject.mjs +0 -463
  273. package/template/.agents/skills/design/impeccable/scripts/live-insert.mjs +0 -292
  274. package/template/.agents/skills/design/impeccable/scripts/live-manual-edit-evidence.mjs +0 -368
  275. package/template/.agents/skills/design/impeccable/scripts/live-poll.mjs +0 -430
  276. package/template/.agents/skills/design/impeccable/scripts/live-resume.mjs +0 -123
  277. package/template/.agents/skills/design/impeccable/scripts/live-server.mjs +0 -1698
  278. package/template/.agents/skills/design/impeccable/scripts/live-status.mjs +0 -71
  279. package/template/.agents/skills/design/impeccable/scripts/live-target.mjs +0 -30
  280. package/template/.agents/skills/design/impeccable/scripts/live-wrap.mjs +0 -927
  281. package/template/.agents/skills/design/impeccable/scripts/live.mjs +0 -334
  282. package/template/.agents/skills/design/impeccable/scripts/modern-screenshot.umd.js +0 -14
  283. package/template/.agents/skills/design/impeccable/scripts/palette.mjs +0 -628
  284. package/template/.agents/skills/design/impeccable/scripts/pin.mjs +0 -224
  285. package/template/.agents/skills/design/impeccable/scripts/serve-question.mjs +0 -1783
  286. package/template/.agents/skills/design/impeccable/scripts/surface-brief.mjs +0 -74
  287. package/template/.agents/skills/design/shadcn/SKILL.md +0 -277
  288. package/template/.agents/skills/design/shadcn/agents/openai.yml +0 -5
  289. package/template/.agents/skills/design/shadcn/assets/shadcn-small.png +0 -0
  290. package/template/.agents/skills/design/shadcn/assets/shadcn.png +0 -0
  291. package/template/.agents/skills/design/shadcn/cli.md +0 -290
  292. package/template/.agents/skills/design/shadcn/customization.md +0 -209
  293. package/template/.agents/skills/design/shadcn/evals/evals.json +0 -77
  294. package/template/.agents/skills/design/shadcn/mcp.md +0 -105
  295. package/template/.agents/skills/design/shadcn/registry.md +0 -277
  296. package/template/.agents/skills/design/shadcn/rules/base-vs-radix.md +0 -306
  297. package/template/.agents/skills/design/shadcn/rules/chat.md +0 -250
  298. package/template/.agents/skills/design/shadcn/rules/composition.md +0 -213
  299. package/template/.agents/skills/design/shadcn/rules/forms.md +0 -192
  300. package/template/.agents/skills/design/shadcn/rules/icons.md +0 -101
  301. package/template/.agents/skills/design/shadcn/rules/styling.md +0 -185
  302. package/template/.agents/skills/find-skills/SKILL.md +0 -141
  303. package/template/.agents/skills/hono/SKILL.md +0 -593
  304. package/template/.agents/skills/integrations/langfuse/SKILL.md +0 -150
  305. package/template/.agents/skills/integrations/langfuse/references/ci-cd.md +0 -41
  306. package/template/.agents/skills/integrations/langfuse/references/cli.md +0 -60
  307. package/template/.agents/skills/integrations/langfuse/references/create-dataset.md +0 -35
  308. package/template/.agents/skills/integrations/langfuse/references/error-analysis.md +0 -87
  309. package/template/.agents/skills/integrations/langfuse/references/instrumentation.md +0 -121
  310. package/template/.agents/skills/integrations/langfuse/references/judge-calibration.md +0 -293
  311. package/template/.agents/skills/integrations/langfuse/references/prompt-engineering.md +0 -35
  312. package/template/.agents/skills/integrations/langfuse/references/prompt-migration.md +0 -79
  313. package/template/.agents/skills/integrations/langfuse/references/sdk-upgrade.md +0 -34
  314. package/template/.agents/skills/integrations/langfuse/references/setting-up-evals.md +0 -65
  315. package/template/.agents/skills/integrations/langfuse/references/skill-feedback.md +0 -35
  316. package/template/.agents/skills/integrations/langfuse/references/trace-evaluator-upgrade.md +0 -76
  317. package/template/.agents/skills/integrations/langfuse/references/user-feedback.md +0 -66
  318. package/template/.agents/skills/integrations/langfuse/references/v4-project-migration.md +0 -75
  319. package/template/.agents/skills/integrations/posthog/SKILL.md +0 -102
  320. package/template/.agents/skills/integrations/posthog/references/error-tracking-alerts.md +0 -63
  321. package/template/.agents/skills/integrations/posthog/references/error-tracking-assigning-issues.md +0 -77
  322. package/template/.agents/skills/integrations/posthog/references/error-tracking-fingerprints.md +0 -57
  323. package/template/.agents/skills/integrations/posthog/references/error-tracking-monitoring.md +0 -140
  324. package/template/.agents/skills/integrations/posthog/references/error-tracking-nextjs.md +0 -490
  325. package/template/.agents/skills/integrations/posthog/references/error-tracking-source-maps.md +0 -45
  326. package/template/.agents/skills/integrations/posthog/references/feature-flags-best-practices.md +0 -139
  327. package/template/.agents/skills/integrations/posthog/references/feature-flags-react.md +0 -302
  328. package/template/.agents/skills/integrations/posthog/references/identify-users.md +0 -202
  329. package/template/.agents/skills/integrations/posthog/references/integration-example.md +0 -706
  330. package/template/.agents/skills/integrations/posthog/references/integration-nextjs.md +0 -385
  331. package/template/.agents/skills/integrations/posthog/references/integration-step-1-begin.md +0 -43
  332. package/template/.agents/skills/integrations/posthog/references/integration-step-2-edit.md +0 -37
  333. package/template/.agents/skills/integrations/posthog/references/integration-step-3-revise.md +0 -22
  334. package/template/.agents/skills/integrations/posthog/references/integration-step-4-conclude.md +0 -38
  335. package/template/.agents/skills/integrations/posthog/references/llm-analytics-anthropic.md +0 -200
  336. package/template/.agents/skills/integrations/posthog/references/llm-analytics-basics.md +0 -62
  337. package/template/.agents/skills/integrations/posthog/references/llm-analytics-costs.md +0 -197
  338. package/template/.agents/skills/integrations/posthog/references/llm-analytics-manual-capture.md +0 -397
  339. package/template/.agents/skills/integrations/posthog/references/llm-analytics-traces.md +0 -98
  340. package/template/.agents/skills/integrations/posthog/references/llm-analytics-vercel-ai.md +0 -120
  341. package/template/.agents/skills/repo/repo-ci/SKILL.md +0 -265
  342. package/template/.agents/skills/repo/repo-init-next-js/SKILL.md +0 -129
  343. package/template/.agents/skills/repo/repo-init-next-js/references/file-contents.md +0 -800
  344. package/template/.agents/skills/repo/repo-init-next-js/scripts/setup.sh +0 -47
  345. package/template/.agents/skills/repo/repo-init-node/SKILL.md +0 -196
  346. package/template/.agents/skills/skill-creator/LICENSE.txt +0 -202
  347. package/template/.agents/skills/skill-creator/SKILL.md +0 -485
  348. package/template/.agents/skills/skill-creator/agents/analyzer.md +0 -274
  349. package/template/.agents/skills/skill-creator/agents/comparator.md +0 -202
  350. package/template/.agents/skills/skill-creator/agents/grader.md +0 -223
  351. package/template/.agents/skills/skill-creator/assets/eval_review.html +0 -146
  352. package/template/.agents/skills/skill-creator/eval-viewer/generate_review.py +0 -471
  353. package/template/.agents/skills/skill-creator/eval-viewer/viewer.html +0 -1325
  354. package/template/.agents/skills/skill-creator/references/schemas.md +0 -430
  355. package/template/.agents/skills/skill-creator/scripts/__init__.py +0 -0
  356. package/template/.agents/skills/skill-creator/scripts/aggregate_benchmark.py +0 -401
  357. package/template/.agents/skills/skill-creator/scripts/generate_report.py +0 -326
  358. package/template/.agents/skills/skill-creator/scripts/improve_description.py +0 -247
  359. package/template/.agents/skills/skill-creator/scripts/package_skill.py +0 -136
  360. package/template/.agents/skills/skill-creator/scripts/quick_validate.py +0 -103
  361. package/template/.agents/skills/skill-creator/scripts/run_eval.py +0 -310
  362. package/template/.agents/skills/skill-creator/scripts/run_loop.py +0 -328
  363. package/template/.agents/skills/skill-creator/scripts/utils.py +0 -47
  364. package/template/.agents/skills/supabase/CHANGELOG.md +0 -78
  365. package/template/.agents/skills/supabase/SKILL.md +0 -149
  366. package/template/.agents/skills/supabase/assets/feedback-issue-template.md +0 -17
  367. package/template/.agents/skills/supabase/references/skill-feedback.md +0 -17
  368. package/template/.agents/skills/supabase-postgres-best-practices/CHANGELOG.md +0 -73
  369. package/template/.agents/skills/supabase-postgres-best-practices/SKILL.md +0 -64
  370. package/template/.agents/skills/supabase-postgres-best-practices/references/_contributing.md +0 -170
  371. package/template/.agents/skills/supabase-postgres-best-practices/references/_sections.md +0 -39
  372. package/template/.agents/skills/supabase-postgres-best-practices/references/_template.md +0 -34
  373. package/template/.agents/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +0 -55
  374. package/template/.agents/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +0 -49
  375. package/template/.agents/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +0 -46
  376. package/template/.agents/skills/supabase-postgres-best-practices/references/conn-limits.md +0 -44
  377. package/template/.agents/skills/supabase-postgres-best-practices/references/conn-pooling.md +0 -41
  378. package/template/.agents/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +0 -46
  379. package/template/.agents/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +0 -54
  380. package/template/.agents/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +0 -53
  381. package/template/.agents/skills/supabase-postgres-best-practices/references/data-pagination.md +0 -50
  382. package/template/.agents/skills/supabase-postgres-best-practices/references/data-upsert.md +0 -50
  383. package/template/.agents/skills/supabase-postgres-best-practices/references/lock-advisory.md +0 -56
  384. package/template/.agents/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +0 -68
  385. package/template/.agents/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +0 -50
  386. package/template/.agents/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +0 -54
  387. package/template/.agents/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +0 -45
  388. package/template/.agents/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +0 -55
  389. package/template/.agents/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +0 -55
  390. package/template/.agents/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +0 -44
  391. package/template/.agents/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +0 -40
  392. package/template/.agents/skills/supabase-postgres-best-practices/references/query-index-types.md +0 -48
  393. package/template/.agents/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +0 -43
  394. package/template/.agents/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +0 -45
  395. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-constraints.md +0 -80
  396. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-data-types.md +0 -46
  397. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +0 -59
  398. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +0 -55
  399. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-partitioning.md +0 -55
  400. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +0 -61
  401. package/template/.agents/skills/supabase-postgres-best-practices/references/security-privileges.md +0 -54
  402. package/template/.agents/skills/supabase-postgres-best-practices/references/security-rls-basics.md +0 -50
  403. package/template/.agents/skills/supabase-postgres-best-practices/references/security-rls-performance.md +0 -63
  404. package/template/.agents/skills/writing/humanizer/LICENSE +0 -21
  405. package/template/.agents/skills/writing/humanizer/README.md +0 -209
  406. package/template/.agents/skills/writing/humanizer/SKILL.md +0 -456
  407. package/template/.agents/upstreams.json +0 -115
@@ -1,564 +0,0 @@
1
- /**
2
- * font-fingerprint: size-invariant, text-robust shape features for lettering
3
- * in a raster (a comp crop or a rendered sample). fingerprint(img) returns the
4
- * feature vector; distance(a, b) compares two vectors over noise-normalized,
5
- * weighted features. Used by font-match.mjs (comp measurement and ranking)
6
- * and by the catalog index build (scripts/build-font-index.mjs at the repo root). Depends only on
7
- * lib/image-metrics.mjs and lib/raster.mjs.
8
- *
9
- * Every measure is taken per text line and normalized by R, the line's
10
- * reference height (median of the tallest column heights above the baseline:
11
- * the cap line on an all-caps line, the ascender line on a mixed line), so
12
- * the same face gives the same numbers at any point size; per-glyph measures
13
- * are medians so the numbers survive a change of text. Small crops are
14
- * upsampled (bilinear) so R is at least 24px, and stroke runs are measured
15
- * with antialiased edge pixels counted by coverage, so stem widths do not
16
- * fatten at small sizes.
17
- *
18
- * Features (all in R units unless noted; null when not measurable):
19
- * advance/advTall/advX median glyph width over baseline glyphs / tall glyphs / x-height glyphs
20
- * advCV spread of glyph widths (std/median): mono ~0.15, sans ~0.3, script > 0.5
21
- * gap median inter-glyph gap
22
- * xRatio x-line / R (null on all-caps lines)
23
- * descRatio descender depth (90th pct)
24
- * stemW median horizontal ink run in the x band (stem width)
25
- * contrast stem width / median thin (vertical) run: didone high, grotesque ~1
26
- * serif foot width / mid-stem width on stems that reach the baseline
27
- * roundFrac fraction of glyphs with bbox aspect > 0.9
28
- * densTall / densX ink / bbox area for tall / x-height glyphs (weight)
29
- * runDensity horizontal ink runs per row per R of line width (stroke busyness)
30
- * vprof0..9 normalized vertical ink profile from 0.35R below baseline to 1.05R above
31
- * hrun25/50/75/90 quantiles of horizontal run lengths over the letter body
32
- * vrun25/50/75/90 quantiles of vertical run lengths over the whole line
33
- * colq25/75 quantiles of column heights above the baseline
34
- * wq25/75 quantiles of glyph widths
35
- * Also returned: lines, glyphs, capHeightPx (R in source pixels), allCaps, inkIsDark,
36
- * upsampled, weight (densTall, so v1 callers keep a weight field).
37
- */
38
- import { toGray } from './image-metrics.mjs';
39
- import { resize } from './raster.mjs';
40
-
41
- function otsu(gray) {
42
- const hist = new Float64Array(256);
43
- for (let i = 0; i < gray.data.length; i++) hist[Math.max(0, Math.min(255, Math.round(gray.data[i])))]++;
44
- const total = gray.data.length;
45
- let sum = 0; for (let i = 0; i < 256; i++) sum += i * hist[i];
46
- let sumB = 0, wB = 0, best = 0, thr = 128;
47
- for (let t = 0; t < 256; t++) {
48
- wB += hist[t]; if (!wB) continue;
49
- const wF = total - wB; if (!wF) break;
50
- sumB += t * hist[t];
51
- const mB = sumB / wB, mF = (sum - sumB) / wF;
52
- const between = wB * wF * (mB - mF) ** 2;
53
- if (between > best) { best = between; thr = t; }
54
- }
55
- return thr;
56
- }
57
-
58
- const med = (a) => { if (!a.length) return null; const s = [...a].sort((p, q) => p - q); const m = s.length >> 1; return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2; };
59
- const pct = (a, p) => { if (!a.length) return null; const s = [...a].sort((p, q) => p - q); return s[Math.min(s.length - 1, Math.floor(p * s.length))]; };
60
- const mean = (a) => (a.length ? a.reduce((s, x) => s + x, 0) / a.length : null);
61
-
62
- /** Binarize; returns { W, H, ink: Uint8Array, inkIsDark }. */
63
- function binarize(img) {
64
- const g = toGray(img);
65
- let thr = otsu(g);
66
- let dark = 0; for (let i = 0; i < g.data.length; i++) if (g.data[i] < thr) dark++;
67
- // a two-level raster (no antialiasing) puts the Otsu threshold on the dark
68
- // level itself; step it up so that level counts as ink
69
- if (!dark) { thr += 1; for (let i = 0; i < g.data.length; i++) if (g.data[i] < thr) dark++; }
70
- const inkIsDark = dark <= g.data.length / 2;
71
- const ink = new Uint8Array(g.data.length);
72
- let sI = 0, nI = 0, sG = 0, nG = 0;
73
- for (let i = 0; i < g.data.length; i++) {
74
- const on = (inkIsDark ? g.data[i] < thr : g.data[i] >= thr) ? 1 : 0;
75
- ink[i] = on;
76
- if (on) { sI += g.data[i]; nI++; } else { sG += g.data[i]; nG++; }
77
- }
78
- const inkLevel = nI ? sI / nI : (inkIsDark ? 0 : 255), groundLevel = nG ? sG / nG : (inkIsDark ? 255 : 0);
79
- // coverage per pixel: 0 = ground, 1 = ink, linear between the two class means, so
80
- // antialiased edge pixels count fractionally and stroke widths do not fatten at small sizes
81
- const covA = new Float32Array(g.data.length);
82
- const den = groundLevel - inkLevel || 1;
83
- for (let i = 0; i < g.data.length; i++) covA[i] = Math.max(0, Math.min(1, (groundLevel - g.data[i]) / den));
84
- const cov = (i) => covA[i];
85
- return { W: g.width, H: g.height, ink, inkIsDark, cov, covA };
86
- }
87
-
88
- /** Text lines from the row-ink profile (same rules as font-match v1). */
89
- function findLines(bin) {
90
- const { W, H, ink } = bin;
91
- // Columns inked top to bottom (a rule, a black margin, a page edge) span
92
- // every line and would fuse them into one run: leave them out of the row
93
- // profile. Lettering never fills a column for more than ~85% of the crop.
94
- const colInk = new Uint32Array(W);
95
- for (let y = 0; y < H; y++) { const o = y * W; for (let x = 0; x < W; x++) colInk[x] += ink[o + x]; }
96
- const colOk = new Uint8Array(W);
97
- let okCount = 0;
98
- for (let x = 0; x < W; x++) { if (colInk[x] < H * 0.85) { colOk[x] = 1; okCount++; } }
99
- if (!okCount) return { lines: [], rowInk: new Uint32Array(H) };
100
- const rowInk = new Uint32Array(H);
101
- for (let y = 0; y < H; y++) { let c = 0; const o = y * W; for (let x = 0; x < W; x++) if (colOk[x]) c += ink[o + x]; rowInk[y] = c; }
102
- const floor = Math.max(1, W * 0.004);
103
- const runs = [];
104
- let y = 0;
105
- while (y < H) {
106
- if (rowInk[y] > floor) {
107
- const y0 = y; while (y < H && (rowInk[y] > floor || (y + 1 < H && rowInk[y + 1] > floor))) y++;
108
- if (y - y0 >= 4) runs.push({ y0, y1: y });
109
- } else y++;
110
- }
111
- const lines = [];
112
- for (const run of runs) {
113
- let peak = 0; for (let yy = run.y0; yy < run.y1; yy++) peak = Math.max(peak, rowInk[yy]);
114
- const valley = peak * 0.15;
115
- let start = run.y0, inValley = false, valleyStart = 0;
116
- for (let yy = run.y0; yy < run.y1; yy++) {
117
- const low = rowInk[yy] < valley;
118
- if (low && !inValley) { inValley = true; valleyStart = yy; }
119
- if (!low && inValley) {
120
- inValley = false;
121
- if (yy - valleyStart >= 3 && valleyStart - start >= 4) { lines.push({ y0: start, y1: valleyStart, run }); start = yy; }
122
- }
123
- }
124
- if (run.y1 - start >= 4) lines.push({ y0: start, y1: run.y1, run });
125
- }
126
- // A piece split off inside one run with a fraction of the ink of the text
127
- // lines is not a line: a thin band of ascenders or tittles above the x band
128
- // (few letters reach it, so the valley rule fires) or a stray rule. Ascender
129
- // bands merge back into the line below them; anything else is dropped.
130
- for (const ln of lines) { let m = 0; for (let yy = ln.y0; yy < ln.y1; yy++) m += rowInk[yy]; ln.mass = m; }
131
- // A drawing or photo sharing the crop with body copy is one tall, massive
132
- // 'line' that would carry maxMass and drop every real line under the 30%
133
- // rule (a 461x307 thread crop measured as one 160px 'cap' off a
134
- // carburetor drawing). When several lines exist, ones far taller than the
135
- // median are not lettering: leave them out of the mass reference and out
136
- // of the result.
137
- // The median is taken over lines carrying real mass (rule slivers and
138
- // tittles do not vote), and needs three of them: two 145px headline lines
139
- // above a 26px artist line were dropped as 'tall' against a median pulled
140
- // to 28 by three slivers.
141
- const massMax = Math.max(1, ...lines.map((l) => l.mass));
142
- const real = lines.filter((l) => l.mass >= massMax * 0.05);
143
- if (real.length >= 3) {
144
- const hs = real.map((l) => l.y1 - l.y0).sort((a, b) => a - b);
145
- const medH = hs[Math.floor(hs.length / 2)];
146
- for (const ln of lines) if (ln.y1 - ln.y0 > medH * 3) ln.tall = true;
147
- }
148
- const maxMass = Math.max(0, ...lines.filter((l) => !l.tall).map((l) => l.mass));
149
- const merged = [];
150
- for (let i = 0; i < lines.length; i++) {
151
- const ln = lines[i];
152
- if (ln.tall) continue;
153
- if (ln.mass >= maxMass * 0.3) { merged.push({ y0: ln.y0, y1: ln.y1, mass: ln.mass }); continue; }
154
- const next = lines[i + 1];
155
- if (next && next.run === ln.run && next.mass >= maxMass * 0.3 && (ln.y1 - ln.y0) <= (next.y1 - next.y0) * 0.5) { next.y0 = ln.y0; }
156
- }
157
- return { lines: merged, rowInk };
158
- }
159
-
160
- /** Feature names in fingerprint order (used by distance). */
161
- const VBINS = 10, HQ = [0.25, 0.5, 0.75, 0.9];
162
- export const FEATURES = ['advance', 'advTall', 'advX', 'advCV', 'gap', 'xRatio', 'descRatio', 'stemW', 'contrast', 'serif', 'roundFrac', 'densTall', 'densX', 'runDensity',
163
- ...Array.from({ length: VBINS }, (_, i) => `vprof${i}`), ...HQ.map((q) => `hrun${Math.round(q * 100)}`), ...HQ.map((q) => `vrun${Math.round(q * 100)}`), 'colq25', 'colq75', 'wq25', 'wq75'];
164
-
165
- /** Center of the densest window of width tol in a list of values, and its count. */
166
- function modeOf(vals, tol) {
167
- let best = null, bestC = -1;
168
- const s = [...vals].sort((a, b) => a - b);
169
- let j = 0;
170
- for (let i = 0; i < s.length; i++) {
171
- while (s[i] - s[j] > tol) j++;
172
- const c = i - j + 1;
173
- if (c > bestC) { bestC = c; best = (s[i] + s[j]) / 2; }
174
- }
175
- return { v: best, n: bestC };
176
- }
177
-
178
- /**
179
- * Per-line vertical metrics from column extrema, which do not need glyphs to
180
- * be separable. baseline = mode of column bottoms. R (the reference height)
181
- * is the top line of the tallest cluster: the cap line on an all-caps line,
182
- * the ascender line (or the cap line when caps are taller) on a mixed line.
183
- * The x-line is a second mode of column heights well below R; when there is
184
- * none the line is read as all-caps.
185
- */
186
- function lineMetrics(bin, ln) {
187
- const { W, ink, cov } = bin;
188
- const cols = [];
189
- for (let x = 0; x < W; x++) {
190
- let top = -1, bot = -1;
191
- for (let yy = ln.y0; yy < ln.y1; yy++) if (ink[yy * W + x]) { if (top < 0) top = yy; bot = yy + 1; }
192
- if (top < 0) continue;
193
- // sub-pixel edges from the antialiased boundary pixel's coverage
194
- const t = top > 0 ? top - cov((top - 1) * W + x) : top;
195
- const b = bot < bin.H ? bot + cov(bot * W + x) : bot;
196
- cols.push({ x, top: t, bot: b });
197
- }
198
- if (cols.length < 8) return null;
199
- const roughH = pct(cols.map((c) => c.bot - c.top), 0.9);
200
- const tol = Math.max(1, Math.round(roughH * 0.04));
201
- const baseF = modeOf(cols.map((c) => c.bot), tol).v;
202
- const base = Math.round(baseF);
203
- const hs = cols.filter((c) => c.bot <= baseF + tol * 1.5).map((c) => baseF - c.top).filter((h) => h > 0);
204
- if (hs.length < 8) return null;
205
- const hMaxAbs = pct(hs, 0.995);
206
- const topCluster = hs.filter((h) => h >= hMaxAbs * 0.94);
207
- const R = med(topCluster);
208
- if (!R || R < 4) return null;
209
- const lowHs = hs.filter((h) => h >= R * 0.3 && h <= R * 0.86);
210
- let xh = null;
211
- if (lowHs.length >= Math.max(6, hs.length * 0.12)) {
212
- const m = modeOf(lowHs, tol);
213
- if (m.n >= Math.max(4, lowHs.length * 0.25)) xh = m.v;
214
- }
215
- const dsc = cols.filter((c) => c.bot > baseF + tol * 1.5 && c.top < baseF - R * 0.3).map((c) => (c.bot - baseF) / R);
216
- const descRatio = dsc.length >= 4 ? pct(dsc, 0.9) : null;
217
- return { base, R, cap: R, xh, descRatio, tol, xL: cols[0].x, xR: cols[cols.length - 1].x + 1, hs, ln };
218
- }
219
-
220
- /** Glyph boxes: column runs of ink inside the x band, so ascender/descender bridges do not merge letters. */
221
- function segment(bin, ln, m) {
222
- const { W, ink } = bin;
223
- const bandTop = Math.max(ln.y0, Math.round(m.base - (m.xh || m.cap * 0.6)));
224
- const bandH = m.base - bandTop;
225
- const thr = 1;
226
- const colBand = new Uint32Array(W);
227
- for (let yy = bandTop; yy < m.base; yy++) { const o = yy * W; for (let x = m.xL; x < m.xR; x++) colBand[x] += ink[o + x]; }
228
- const runs = [];
229
- let x = m.xL;
230
- while (x < m.xR) {
231
- if (colBand[x] >= thr) { const x0 = x; while (x < m.xR && colBand[x] >= thr) x++; runs.push({ x0, x1: x }); } else x++;
232
- }
233
- const out = [];
234
- for (const r of runs) {
235
- let top = -1, bot = -1, area = 0;
236
- for (let yy = ln.y0; yy < ln.y1; yy++) {
237
- let c = 0, cv = 0; const o = yy * W; for (let xx = r.x0; xx < r.x1; xx++) { c += ink[o + xx]; cv += bin.covA[o + xx]; }
238
- if (c) { if (top < 0) top = yy; bot = yy + 1; }
239
- area += cv;
240
- }
241
- if (top >= 0) out.push({ x0: r.x0, x1: r.x1, w: r.x1 - r.x0, top, bot, h: bot - top, area });
242
- }
243
- return out;
244
- }
245
-
246
- function measure(bin, lines) {
247
- const { W, H, ink, covA } = bin;
248
- // run lengths with the antialiased edge pixels counted by coverage
249
- const hLen = (o, x0, x1) => { let s = 0; for (let x = Math.max(0, x0 - 1); x < Math.min(W, x1 + 1); x++) s += covA[o + x]; return s; };
250
- const vLen = (x, y0, y1) => { let s = 0; for (let y = Math.max(0, y0 - 1); y < Math.min(H, y1 + 1); y++) s += covA[y * W + x]; return s; };
251
- let glyphN = 0;
252
- const per = { xh: [], desc: [], runDensity: [] };
253
- let allCapsLines = 0;
254
- const vprof = new Float64Array(VBINS); const hruns = [], vruns = [], colHs = [], widths = [];
255
- const advTall = [], advAll = [], advX = [], gaps = [], stems = [], thins = [], serifR = [], round = [], densTall = [], densX = [];
256
- let capSum = 0, capN = 0;
257
- // One crop, one case. In a multi-line all-caps headline one line can grow a
258
- // spurious x-height from crossbars (the A and E arms of "JAPANESE" at 0.32R)
259
- // while its neighbours report none; that line then measures its stems and
260
- // its x band on the crossbar zone. Lines vote: when most lines see no
261
- // x-height, none does.
262
- const metrics = lines.map((ln) => lineMetrics(bin, ln)).filter(Boolean);
263
- if (metrics.length >= 2) {
264
- const withX = metrics.filter((m) => m.xh).length;
265
- if (withX * 2 <= metrics.length) for (const m of metrics) m.xh = null;
266
- }
267
- for (const m of metrics) {
268
- const ln = m.ln;
269
- const { base, cap, xh, tol, xL, xR } = m;
270
- capSum += cap; capN++;
271
- if (xh) per.xh.push(xh / cap);
272
- if (!xh) allCapsLines++;
273
- if (m.descRatio != null) per.desc.push(m.descRatio);
274
- for (const h of m.hs) colHs.push(h / cap);
275
- // vertical ink profile from 0.35R below the baseline to 1.05R above, VBINS bins
276
- for (let yy = ln.y0; yy < ln.y1; yy++) {
277
- const u = (base - yy - 0.5) / cap; // height above baseline in R units
278
- const bi = Math.floor((u + 0.35) / 1.4 * VBINS);
279
- if (bi < 0 || bi >= VBINS) continue;
280
- let c = 0; const o = yy * W; for (let x = xL; x < xR; x++) c += ink[o + x];
281
- vprof[bi] += c;
282
- }
283
- // horizontal run lengths over the whole line body (x band to cap line), vertical run lengths over all columns
284
- for (let yy = Math.max(ln.y0, Math.round(base - cap)); yy < base; yy++) {
285
- const o = yy * W; let x = xL;
286
- while (x < xR) { if (ink[o + x]) { const x0 = x; while (x < xR && ink[o + x]) x++; hruns.push(hLen(o, x0, x) / cap); } else x++; }
287
- }
288
- for (let x = xL; x < xR; x++) {
289
- let yy = ln.y0;
290
- while (yy < ln.y1) { if (ink[yy * W + x]) { const y0 = yy; while (yy < ln.y1 && ink[yy * W + x]) yy++; vruns.push(vLen(x, y0, yy) / cap); } else yy++; }
291
- }
292
- const gl = segment(bin, ln, m);
293
- const G = gl.filter((g) => g.w >= cap * 0.12 && (base - g.top) >= cap * 0.3);
294
- glyphN += G.length;
295
- const onBase = G.filter((g) => Math.abs(g.bot - base) <= tol * 1.5);
296
- const capG = onBase.filter((g) => base - g.top >= cap * 0.88);
297
- const xs = xh ? onBase.filter((g) => Math.abs(base - g.top - xh) <= Math.max(tol * 1.5, cap * 0.05)) : [];
298
- for (const g of capG) { advTall.push(g.w / cap); densTall.push(g.area / (g.w * g.h)); }
299
- for (const g of xs) { densX.push(g.area / (g.w * g.h)); advX.push(g.w / cap); }
300
- for (const g of onBase) { advAll.push(g.w / cap); widths.push(g.w / cap); round.push(g.w / (base - g.top) > 0.9 ? 1 : 0); }
301
- for (let i = 0; i + 1 < G.length; i++) { const gap = G[i + 1].x0 - G[i].x1; if (gap >= 0 && gap < cap * 0.6) gaps.push(gap / cap); }
302
- const xTop = base - (xh || cap * 0.55);
303
- const bandTop = Math.round(xTop + (base - xTop) * 0.2), bandBot = Math.round(base - (base - xTop) * 0.2);
304
- let runCount = 0, runRows = 0;
305
- for (let yy = bandTop; yy < bandBot; yy++) {
306
- const o = yy * W; let x = xL; runRows++;
307
- while (x < xR) { if (ink[o + x]) { const x0 = x; while (x < xR && ink[o + x]) x++; const L = hLen(o, x0, x); runCount++; if (L < cap * 0.5) stems.push(L / cap); } else x++; }
308
- }
309
- if (runRows) per.runDensity.push((runCount / runRows) / ((xR - xL) / cap));
310
- for (let x = xL; x < xR; x++) {
311
- let yy = ln.y0;
312
- while (yy < ln.y1) { if (ink[yy * W + x]) { const y0 = yy; while (yy < ln.y1 && ink[yy * W + x]) yy++; const L = vLen(x, y0, yy); if (L < cap * 0.35) thins.push(L / cap); } else yy++; }
313
- }
314
- // serif: stems that run straight to the baseline; foot width vs mid-stem width
315
- const runAt = (yy, x) => { const o = yy * W; if (!ink[o + x]) return 0; let a = x, b = x; while (a > xL && ink[o + a - 1]) a--; while (b + 1 < xR && ink[o + b + 1]) b++; return hLen(o, a, b + 1); };
316
- const yMid = Math.round(base - cap * 0.4), yHi = Math.round(base - cap * 0.18), yFoot = base - Math.max(1, Math.round(cap * 0.04));
317
- let x = xL;
318
- while (x < xR) {
319
- let yy = base - 1; if (!ink[yy * W + x]) { x++; continue; }
320
- while (yy > ln.y0 && ink[(yy - 1) * W + x]) yy--;
321
- if (yy > yMid) { x++; continue; }
322
- const x0 = x; x++; while (x < xR && ink[(base - 1) * W + x] && ink[yMid * W + x]) x++;
323
- const xc = Math.round((x0 + x - 1) / 2);
324
- const wMid = runAt(yMid, xc), wHi = runAt(yHi, xc), wFoot = runAt(yFoot, xc);
325
- if (wMid > 0 && wMid < cap * 0.5 && wHi <= wMid * 1.3 && wHi >= wMid * 0.7) serifR.push(wFoot / wMid);
326
- }
327
- }
328
- if (!capN) return null;
329
- const stemW = med(stems), thinW = med(thins);
330
- const advM = med(advAll);
331
- const advSd = advAll.length > 3 ? Math.sqrt(advAll.reduce((s, v) => s + (v - advM) ** 2, 0) / advAll.length) : null;
332
- const vsum = vprof.reduce((s, x) => s + x, 0) || 1;
333
- const extra = {};
334
- for (let i = 0; i < VBINS; i++) extra[`vprof${i}`] = vprof[i] / vsum;
335
- for (const q of HQ) { extra[`hrun${Math.round(q * 100)}`] = pct(hruns, q); extra[`vrun${Math.round(q * 100)}`] = pct(vruns, q); }
336
- extra.colq25 = pct(colHs, 0.25); extra.colq75 = pct(colHs, 0.75);
337
- extra.wq25 = pct(widths, 0.25); extra.wq75 = pct(widths, 0.75);
338
- return {
339
- ...extra,
340
- capHeightPx: capSum / capN,
341
- glyphs: glyphN,
342
- advance: advM,
343
- advTall: advTall.length ? med(advTall) : null,
344
- advX: advX.length ? med(advX) : null,
345
- advCV: advSd != null && advM ? advSd / advM : null,
346
- gap: gaps.length ? med(gaps) : 0,
347
- xRatio: per.xh.length ? med(per.xh) : null,
348
- descRatio: per.desc.length ? med(per.desc) : null,
349
- allCaps: allCapsLines * 2 > capN,
350
- runDensity: med(per.runDensity),
351
- stemW,
352
- contrast: stemW && thinW ? stemW / thinW : null,
353
- serif: serifR.length >= 3 ? med(serifR) : null,
354
- roundFrac: round.length ? mean(round) : null,
355
- densTall: densTall.length ? med(densTall) : null,
356
- densX: densX.length ? med(densX) : null,
357
- };
358
- }
359
-
360
- /**
361
- * fingerprint(img) -> features, or null when no lettering is found. Upsamples (bilinear) when the
362
- * cap height is under 24px so runs and edges are measured on finer pixels.
363
- */
364
- /**
365
- * Keep the dominant lettering in a region crop: the lines whose cap height is
366
- * within `tol` of the tallest, clipped horizontally to their own ink. A comp
367
- * region drawn on a 10x10 grid over-covers: the headline crop carries the
368
- * first line of body copy below it and a slice of the neighbouring column,
369
- * and every one of those small letters pulls stem width, run lengths and the
370
- * x-height vote toward a lighter, wider face. Returns { lines, x0, x1 } in
371
- * the binarized image, or null when nothing survives.
372
- */
373
- export function isolateDominant(bin, lines, { tol = 0.28 } = {}) {
374
- const ms = lines.map((ln) => ({ ln, m: lineMetrics(bin, ln) })).filter((x) => x.m);
375
- if (!ms.length) return null;
376
- // The dominant class is the one holding most of the ink, not the tallest
377
- // line: a body-copy crop that clips the last line of the headline above it
378
- // is body copy. Cluster caps within tol of each other and pick the cluster
379
- // with the most ink mass; the tallest wins only a tie.
380
- const clusters = [];
381
- for (const x of [...ms].sort((a, b) => b.m.cap - a.m.cap)) {
382
- const c = clusters.find((cl) => Math.abs(cl.cap - x.m.cap) <= cl.cap * tol);
383
- if (c) { c.items.push(x); c.mass += x.ln.mass || 0; } else clusters.push({ cap: x.m.cap, items: [x], mass: x.ln.mass || 0 });
384
- }
385
- // Mass per line-height, so one heavy display line does not outvote five
386
- // lines of body copy; and a cluster of a single clipped line never wins
387
- // over a cluster of three or more.
388
- for (const c of clusters) { c.rows = c.items.reduce((n, x) => n + (x.ln.y1 - x.ln.y0), 0); c.density = c.mass / Math.max(1, c.rows); c.n = c.items.length; }
389
- clusters.sort((a, b) => {
390
- const aMulti = a.n >= 3, bMulti = b.n >= 3;
391
- if (aMulti !== bMulti) return aMulti ? -1 : 1;
392
- return (b.mass - a.mass) || (b.cap - a.cap);
393
- });
394
- const keep = clusters[0].items;
395
- const capMax = Math.max(...keep.map((x) => x.m.cap));
396
- // horizontal extent of the kept lines' tallest ink columns only: a small
397
- // column of body text beside the headline shares its rows but not its height
398
- const { W, ink } = bin;
399
- let x0 = W, x1 = 0;
400
- for (const { ln, m } of keep) {
401
- const top = Math.round(m.base - m.cap * 0.75);
402
- for (let x = m.xL; x < m.xR; x++) {
403
- let tall = false;
404
- for (let y = top; y < m.base && !tall; y++) if (ink[y * W + x]) tall = true;
405
- if (!tall) continue;
406
- // a column is headline ink when a run of at least 0.5 cap of ink stands in it
407
- let run = 0, best = 0;
408
- for (let y = ln.y0; y < ln.y1; y++) { if (ink[y * W + x]) { run++; if (run > best) best = run; } else run = 0; }
409
- if (best >= m.cap * 0.5) { if (x < x0) x0 = x; if (x + 1 > x1) x1 = x + 1; }
410
- }
411
- }
412
- if (x1 <= x0) return null;
413
- // grow the box by half a cap so glyph sides and the tracking gap survive
414
- const pad = Math.round(capMax * 0.5);
415
- return { lines: keep.map((x) => x.ln), x0: Math.max(0, x0 - pad), x1: Math.min(W, x1 + pad), dropped: ms.length - keep.length };
416
- }
417
-
418
- function maskOutside(bin, x0, x1, lines) {
419
- const { W, H, ink, covA } = bin;
420
- const keepRow = new Uint8Array(H);
421
- for (const ln of lines) for (let y = ln.y0; y < ln.y1; y++) keepRow[y] = 1;
422
- const ink2 = new Uint8Array(ink.length), cov2 = new Float32Array(covA.length);
423
- for (let y = 0; y < H; y++) {
424
- if (!keepRow[y]) continue;
425
- for (let x = x0; x < x1; x++) { const i = y * W + x; ink2[i] = ink[i]; cov2[i] = covA[i]; }
426
- }
427
- return { ...bin, ink: ink2, covA: cov2, cov: (i) => cov2[i] };
428
- }
429
-
430
- export function fingerprint(img, { minCap = 24, minGlyphs = 3, isolate = true } = {}) {
431
- let bin = binarize(img);
432
- let { lines } = findLines(bin);
433
- if (!lines.length) return null;
434
- let isolated = 0;
435
- if (isolate && lines.length > 1) {
436
- const iso = isolateDominant(bin, lines);
437
- if (iso && (iso.dropped > 0 || iso.x1 - iso.x0 < bin.W * 0.9)) {
438
- bin = maskOutside(bin, iso.x0, iso.x1, iso.lines);
439
- lines = iso.lines;
440
- isolated = iso.dropped;
441
- }
442
- }
443
- let f = measure(bin, lines);
444
- // fewer than minGlyphs separable glyphs is not lettering (a rule, a solid
445
- // bar, one letterform): callers read null as "no separable lettering"
446
- if (!f || f.glyphs < minGlyphs) return null;
447
- let scale = 1;
448
- if (f.capHeightPx < minCap && f.capHeightPx >= 4) {
449
- scale = Math.min(4, Math.ceil(minCap / f.capHeightPx));
450
- const up = resize(img, img.width * scale, img.height * scale);
451
- bin = binarize(up);
452
- lines = findLines(bin).lines;
453
- // the upsample re-reads the whole crop: isolate again so the clipped
454
- // headline or the drawing does not come back at scale
455
- if (isolate && lines.length > 1) {
456
- const iso2 = isolateDominant(bin, lines);
457
- if (iso2 && (iso2.dropped > 0 || iso2.x1 - iso2.x0 < bin.W * 0.9)) { bin = maskOutside(bin, iso2.x0, iso2.x1, iso2.lines); lines = iso2.lines; isolated = Math.max(isolated, iso2.dropped); }
458
- }
459
- const f2 = lines.length ? measure(bin, lines) : null;
460
- if (f2) f = f2;
461
- else scale = 1;
462
- }
463
- const r = { lines: lines.length, glyphs: f.glyphs, capHeightPx: +(f.capHeightPx / scale).toFixed(1), inkIsDark: bin.inkIsDark, upsampled: scale > 1, allCaps: f.allCaps, isolatedFrom: isolated, weight: f.densTall == null && f.densX == null ? null : +(f.densTall ?? f.densX).toFixed(4) };
464
- for (const k of FEATURES) r[k] = f[k] == null ? null : +f[k].toFixed(4);
465
- return r;
466
- }
467
-
468
- /**
469
- * Distance normalization fitted on 299 held-out probes (150 at ~30px cap, 149
470
- * at ~14px, text different from the index text) against a 3,092-entry Google
471
- * Fonts index: std = within-family noise (1.4826 x median |probe - own index
472
- * entry|, floored at 5% of the catalog IQR spread), w = group weight from
473
- * coordinate descent on top-5 family recall. mean is unused by the distance.
474
- */
475
- export const STATS = {
476
- advance: { std: 0.07648, w: 0 },
477
- advTall: { std: 0.25331, w: 0 },
478
- advX: { std: 0.05144, w: 1.5 },
479
- advCV: { std: 0.0857, w: 1 },
480
- gap: { std: 0.02668, w: 1 },
481
- xRatio: { std: 0.02315, w: 1 },
482
- descRatio: { std: 0.17831, w: 1 },
483
- stemW: { std: 0.01922, w: 1 },
484
- contrast: { std: 0.05969, w: 3 },
485
- serif: { std: 0.31477, w: 0.5 },
486
- roundFrac: { std: 0.09341, w: 1 },
487
- densTall: { std: 0.05708, w: 2 },
488
- densX: { std: 0.07666, w: 0 },
489
- runDensity: { std: 0.18199, w: 1 },
490
- vprof0: { std: 0.01178, w: 1 },
491
- vprof1: { std: 0.01331, w: 1 },
492
- vprof2: { std: 0.02745, w: 1 },
493
- vprof3: { std: 0.03046, w: 1 },
494
- vprof4: { std: 0.01933, w: 1 },
495
- vprof5: { std: 0.01737, w: 1 },
496
- vprof6: { std: 0.0336, w: 1 },
497
- vprof7: { std: 0.03195, w: 1 },
498
- vprof8: { std: 0.03271, w: 1 },
499
- vprof9: { std: 0.02951, w: 1 },
500
- hrun25: { std: 0.01751, w: 1 },
501
- hrun50: { std: 0.02124, w: 1 },
502
- hrun75: { std: 0.04503, w: 1 },
503
- hrun90: { std: 0.06844, w: 1 },
504
- vrun25: { std: 0.01895, w: 1 },
505
- vrun50: { std: 0.02405, w: 1 },
506
- vrun75: { std: 0.06199, w: 1 },
507
- vrun90: { std: 0.09486, w: 1 },
508
- colq25: { std: 0.02906, w: 1 },
509
- colq75: { std: 0.18204, w: 1 },
510
- wq25: { std: 0.19862, w: 1 },
511
- wq75: { std: 0.09687, w: 1 },
512
- };
513
- export const Z_CLIP = 3;
514
-
515
- /** Weighted L1 over z-scored features; a feature missing on either side is skipped and the weight mass renormalized. */
516
- /**
517
- * The two readings a designer makes before any detail: how wide, how heavy.
518
- * Width from the advance of tall glyphs (all-caps crops) or x-height glyphs;
519
- * weight from ink density of tall glyphs. Both are on the same scale in the
520
- * comp crop and in a catalog render, so their gap is a plain ratio. Distance
521
- * adds a penalty that grows with the ratio's log: a face 50% wider or 35%
522
- * lighter than the comp cannot rank above one that is right on both, whatever
523
- * its run-length profile says. Weighted like three fine features (the width
524
- * gap and the weight gap each score up to zClip x 1.5).
525
- */
526
- export function grossGap(a, b) {
527
- const pick = (f, keys) => { for (const k of keys) if (f[k] != null) return { k, v: f[k] }; return null; };
528
- const wa = pick(a, ['advX', 'advTall', 'advance']), wb = wa ? (b[wa.k] != null ? { k: wa.k, v: b[wa.k] } : null) : null;
529
- const ha = pick(a, ['densTall', 'densX', 'stemW']), hb = ha ? (b[ha.k] != null ? { k: ha.k, v: b[ha.k] } : null) : null;
530
- const gap = (x, y) => (x && y && x.v > 0 && y.v > 0 ? Math.abs(Math.log(y.v / x.v)) : null);
531
- return { width: gap(wa, wb), weight: gap(ha, hb) };
532
- }
533
-
534
- export const GROSS_STD = { width: 0.12, weight: 0.12 }; // one "step" of width class or weight class, in log ratio
535
- export const GROSS_W = 1.5;
536
-
537
- export function distance(a, b, stats = STATS, { p = 1, zClip = Z_CLIP, gross = true } = {}) {
538
- let d = 0, wsum = 0;
539
- if (gross) {
540
- const g = grossGap(a, b);
541
- for (const k of ['width', 'weight']) {
542
- if (g[k] == null) continue;
543
- const z = Math.min(zClip, g[k] / GROSS_STD[k]);
544
- d += GROSS_W * (p === 1 ? z : z * z); wsum += GROSS_W;
545
- }
546
- }
547
- for (const k of FEATURES) {
548
- const s = stats[k]; if (!s || !s.w) continue;
549
- const av = a[k], bv = b[k];
550
- if (av == null || bv == null) continue;
551
- const z = Math.min(zClip, Math.abs(av - bv) / s.std);
552
- d += s.w * (p === 1 ? z : z * z); wsum += s.w;
553
- }
554
- if (!wsum) return Infinity;
555
- const v = d / wsum;
556
- return p === 1 ? v : Math.sqrt(v);
557
- }
558
-
559
- /** Debug: per-line metrics (base, R, xh, mode counts) for a raster. */
560
- export function _debugLines(img) {
561
- const bin = binarize(img);
562
- const { lines } = findLines(bin);
563
- return lines.map((ln) => { const m = lineMetrics(bin, ln); if (!m) return { ln, m: null }; const hs = m.hs.map((h) => +(h / m.R).toFixed(2)).sort((a, b) => a - b); const hist = {}; for (const h of hs) { const b = Math.round(h * 20) / 20; hist[b] = (hist[b] || 0) + 1; } return { y0: ln.y0, y1: ln.y1, base: m.base, R: +m.R.toFixed(1), xh: m.xh && +m.xh.toFixed(1), hist }; });
564
- }