@dsalazardev/ai-hub-cli 0.0.0-stage → 0.1.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 (1288) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +34 -0
  3. package/README.md +64 -2
  4. package/agents/cpp-build-resolver.md +25 -0
  5. package/agents/cpp-reviewer.md +28 -0
  6. package/agents/csharp-reviewer.md +28 -0
  7. package/agents/django-build-resolver.md +25 -0
  8. package/agents/django-reviewer.md +28 -0
  9. package/agents/explorer-domain.md +28 -0
  10. package/agents/fastapi-reviewer.md +28 -0
  11. package/agents/flutter-build-resolver.md +25 -0
  12. package/agents/flutter-reviewer.md +28 -0
  13. package/agents/go-build-resolver.md +25 -0
  14. package/agents/go-reviewer.md +28 -0
  15. package/agents/implementer-backend.md +30 -0
  16. package/agents/implementer-frontend.md +30 -0
  17. package/agents/java-build-resolver.md +26 -0
  18. package/agents/java-reviewer.md +29 -0
  19. package/agents/kotlin-build-resolver.md +25 -0
  20. package/agents/kotlin-reviewer.md +28 -0
  21. package/agents/mle-reviewer.md +28 -0
  22. package/agents/php-reviewer.md +29 -0
  23. package/agents/postgres-reviewer.md +28 -0
  24. package/agents/python-reviewer.md +28 -0
  25. package/agents/pytorch-build-resolver.md +25 -0
  26. package/agents/rag-reviewer.md +28 -0
  27. package/agents/react-build-resolver.md +26 -0
  28. package/agents/react-reviewer.md +29 -0
  29. package/agents/reviewer-architecture.md +32 -0
  30. package/agents/reviewer-tests.md +31 -0
  31. package/agents/rust-build-resolver.md +25 -0
  32. package/agents/rust-reviewer.md +28 -0
  33. package/agents/spec-author.md +36 -0
  34. package/agents/spec-miner.md +14 -0
  35. package/agents/swift-build-resolver.md +25 -0
  36. package/agents/swift-reviewer.md +28 -0
  37. package/agents/typescript-reviewer.md +28 -0
  38. package/agents/vue-reviewer.md +28 -0
  39. package/dist/backup/snapshots.d.ts +24 -0
  40. package/dist/backup/snapshots.js +171 -0
  41. package/dist/bin/ai-hub.d.ts +2 -0
  42. package/dist/bin/ai-hub.js +11 -0
  43. package/dist/cli/args.d.ts +4 -0
  44. package/dist/cli/args.js +63 -0
  45. package/dist/cli/context.d.ts +7 -0
  46. package/dist/cli/context.js +74 -0
  47. package/dist/cli/doctor.d.ts +14 -0
  48. package/dist/cli/doctor.js +200 -0
  49. package/dist/cli/drift.d.ts +2 -0
  50. package/dist/cli/drift.js +34 -0
  51. package/dist/cli/items.d.ts +5 -0
  52. package/dist/cli/items.js +10 -0
  53. package/dist/cli/legacy.d.ts +3 -0
  54. package/dist/cli/legacy.js +18 -0
  55. package/dist/cli/maintenance.d.ts +8 -0
  56. package/dist/cli/maintenance.js +262 -0
  57. package/dist/cli/memory.d.ts +2 -0
  58. package/dist/cli/memory.js +84 -0
  59. package/dist/cli/onboarding.d.ts +17 -0
  60. package/dist/cli/onboarding.js +234 -0
  61. package/dist/cli/run.d.ts +15 -0
  62. package/dist/cli/run.js +183 -0
  63. package/dist/cli/source.d.ts +5 -0
  64. package/dist/cli/source.js +22 -0
  65. package/dist/cli/trunk.d.ts +8 -0
  66. package/dist/cli/trunk.js +54 -0
  67. package/dist/cli/types.d.ts +17 -0
  68. package/dist/cli/types.js +4 -0
  69. package/dist/cli/worktrees.d.ts +11 -0
  70. package/dist/cli/worktrees.js +141 -0
  71. package/dist/engine/engine.d.ts +48 -0
  72. package/dist/engine/engine.js +114 -0
  73. package/dist/engine/gates.d.ts +2 -0
  74. package/dist/engine/gates.js +93 -0
  75. package/dist/frontmatter.d.ts +11 -0
  76. package/dist/frontmatter.js +30 -0
  77. package/dist/harness/guards/adapters/context.d.ts +19 -0
  78. package/dist/harness/guards/adapters/context.js +153 -0
  79. package/dist/harness/guards/adapters/hermes.d.ts +13 -0
  80. package/dist/harness/guards/adapters/hermes.js +18 -0
  81. package/dist/harness/guards/adapters/hooks.d.ts +25 -0
  82. package/dist/harness/guards/adapters/hooks.js +52 -0
  83. package/dist/harness/guards/branch.d.ts +5 -0
  84. package/dist/harness/guards/branch.js +43 -0
  85. package/dist/harness/guards/danger.d.ts +13 -0
  86. package/dist/harness/guards/danger.js +157 -0
  87. package/dist/harness/guards/index.d.ts +6 -0
  88. package/dist/harness/guards/index.js +6 -0
  89. package/dist/harness/guards/preflight.d.ts +14 -0
  90. package/dist/harness/guards/preflight.js +37 -0
  91. package/dist/harness/guards/ship.d.ts +2 -0
  92. package/dist/harness/guards/ship.js +35 -0
  93. package/dist/harness/guards/trunk.d.ts +5 -0
  94. package/dist/harness/guards/trunk.js +41 -0
  95. package/dist/harness/guards/types.d.ts +50 -0
  96. package/dist/harness/guards/types.js +1 -0
  97. package/dist/harness/memory/ledger.d.ts +53 -0
  98. package/dist/harness/memory/ledger.js +136 -0
  99. package/dist/harness/memory/state.d.ts +35 -0
  100. package/dist/harness/memory/state.js +121 -0
  101. package/dist/harness/worktree/reaper.d.ts +32 -0
  102. package/dist/harness/worktree/reaper.js +54 -0
  103. package/dist/manifest/build.d.ts +1 -0
  104. package/dist/manifest/build.js +1 -0
  105. package/dist/manifest/cli.d.ts +2 -0
  106. package/dist/manifest/cli.js +44 -0
  107. package/dist/manifest/dispatch.d.ts +13 -0
  108. package/dist/manifest/dispatch.js +36 -0
  109. package/dist/manifest/labels.d.ts +15 -0
  110. package/dist/manifest/labels.js +70 -0
  111. package/dist/manifest/manifest.d.ts +113 -0
  112. package/dist/manifest/manifest.js +202 -0
  113. package/dist/package-info.d.ts +4 -0
  114. package/dist/package-info.js +7 -0
  115. package/dist/paths.d.ts +33 -0
  116. package/dist/paths.js +123 -0
  117. package/dist/presets/detect.d.ts +24 -0
  118. package/dist/presets/detect.js +116 -0
  119. package/dist/presets/materialize.d.ts +18 -0
  120. package/dist/presets/materialize.js +101 -0
  121. package/dist/presets/resolve.d.ts +37 -0
  122. package/dist/presets/resolve.js +83 -0
  123. package/dist/presets/schema.d.ts +57 -0
  124. package/dist/presets/schema.js +68 -0
  125. package/dist/source-checkout.d.ts +8 -0
  126. package/dist/source-checkout.js +19 -0
  127. package/dist/sync/config.d.ts +47 -0
  128. package/dist/sync/config.js +85 -0
  129. package/dist/sync/lock.d.ts +16 -0
  130. package/dist/sync/lock.js +43 -0
  131. package/dist/sync/render.d.ts +18 -0
  132. package/dist/sync/render.js +255 -0
  133. package/dist/sync/sync.d.ts +38 -0
  134. package/dist/sync/sync.js +288 -0
  135. package/dist/sync/targets.d.ts +24 -0
  136. package/dist/sync/targets.js +78 -0
  137. package/engine/coupling.json +4 -0
  138. package/engine/engine.yaml +250 -0
  139. package/engine/legacy-paths.json +4 -0
  140. package/engine/preflight.md +11 -0
  141. package/manifest.json +393 -0
  142. package/package.json +61 -4
  143. package/presets/angular-express/harness/rules/01-zero-any.sh +25 -0
  144. package/presets/angular-express/harness/rules/02-no-ng-module.sh +21 -0
  145. package/presets/angular-express/harness/rules/03-no-constructor-injection.sh +21 -0
  146. package/presets/angular-express/harness/rules/04-no-legacy-directives.sh +21 -0
  147. package/presets/angular-express/harness/rules/05-no-dolibarrdal-inheritance.sh +25 -0
  148. package/presets/angular-express/harness/rules/06-no-todos.sh +22 -0
  149. package/presets/angular-express/harness/run-all.sh +13 -0
  150. package/presets/angular-express/preset.yaml +55 -0
  151. package/presets/angular-express/references/common-imports-catalog.md +146 -0
  152. package/presets/angular-express/references/fichajes-migration-notes.md +57 -0
  153. package/presets/angular-express/references/module-implementation-flow.md +260 -0
  154. package/presets/angular-express/references/products-coding-standard.md +143 -0
  155. package/presets/angular-express/references/products-module-blueprint.md +860 -0
  156. package/presets/angular-express/references/replicate-module-checklist.md +72 -0
  157. package/presets/angular-express/references/users-migration-notes.md +172 -0
  158. package/presets/angular-express/rules/architecture.md +8 -0
  159. package/presets/angular-express/rules/backend.md +4 -0
  160. package/presets/angular-express/rules/frontend.md +53 -0
  161. package/presets/angular-express/skills/angular-developer/SKILL.md +130 -0
  162. package/presets/angular-express/skills/angular-developer/references/angular-animations.md +160 -0
  163. package/presets/angular-express/skills/angular-developer/references/angular-aria.md +410 -0
  164. package/presets/angular-express/skills/angular-developer/references/cli.md +86 -0
  165. package/presets/angular-express/skills/angular-developer/references/component-harnesses.md +59 -0
  166. package/presets/angular-express/skills/angular-developer/references/component-styling.md +91 -0
  167. package/presets/angular-express/skills/angular-developer/references/components.md +117 -0
  168. package/presets/angular-express/skills/angular-developer/references/creating-services.md +97 -0
  169. package/presets/angular-express/skills/angular-developer/references/data-resolvers.md +69 -0
  170. package/presets/angular-express/skills/angular-developer/references/define-routes.md +67 -0
  171. package/presets/angular-express/skills/angular-developer/references/defining-providers.md +72 -0
  172. package/presets/angular-express/skills/angular-developer/references/di-fundamentals.md +120 -0
  173. package/presets/angular-express/skills/angular-developer/references/e2e-testing.md +66 -0
  174. package/presets/angular-express/skills/angular-developer/references/effects.md +83 -0
  175. package/presets/angular-express/skills/angular-developer/references/hierarchical-injectors.md +43 -0
  176. package/presets/angular-express/skills/angular-developer/references/host-elements.md +80 -0
  177. package/presets/angular-express/skills/angular-developer/references/injection-context.md +63 -0
  178. package/presets/angular-express/skills/angular-developer/references/inputs.md +101 -0
  179. package/presets/angular-express/skills/angular-developer/references/linked-signal.md +59 -0
  180. package/presets/angular-express/skills/angular-developer/references/loading-strategies.md +61 -0
  181. package/presets/angular-express/skills/angular-developer/references/mcp.md +106 -0
  182. package/presets/angular-express/skills/angular-developer/references/migrations.md +30 -0
  183. package/presets/angular-express/skills/angular-developer/references/navigate-to-routes.md +69 -0
  184. package/presets/angular-express/skills/angular-developer/references/outputs.md +86 -0
  185. package/presets/angular-express/skills/angular-developer/references/reactive-forms.md +122 -0
  186. package/presets/angular-express/skills/angular-developer/references/rendering-strategies.md +44 -0
  187. package/presets/angular-express/skills/angular-developer/references/resource.md +74 -0
  188. package/presets/angular-express/skills/angular-developer/references/route-animations.md +56 -0
  189. package/presets/angular-express/skills/angular-developer/references/route-guards.md +52 -0
  190. package/presets/angular-express/skills/angular-developer/references/router-lifecycle.md +45 -0
  191. package/presets/angular-express/skills/angular-developer/references/router-testing.md +87 -0
  192. package/presets/angular-express/skills/angular-developer/references/show-routes-with-outlets.md +68 -0
  193. package/presets/angular-express/skills/angular-developer/references/signal-forms.md +897 -0
  194. package/presets/angular-express/skills/angular-developer/references/signals-overview.md +94 -0
  195. package/presets/angular-express/skills/angular-developer/references/tailwind-css.md +69 -0
  196. package/presets/angular-express/skills/angular-developer/references/template-driven-forms.md +114 -0
  197. package/presets/angular-express/skills/angular-developer/references/testing-fundamentals.md +66 -0
  198. package/presets/angular-express/skills/angular-new-app/SKILL.md +62 -0
  199. package/presets/angular-express/skills/frontend-design/LICENSE.txt +177 -0
  200. package/presets/angular-express/skills/frontend-design/SKILL.md +55 -0
  201. package/presets/angular-express/skills/onna-architecture-angular/SKILL.md +229 -0
  202. package/presets/angular-express/skills/onna-architecture-bff/SKILL.md +446 -0
  203. package/presets/angular-express/skills/onna-architecture-common/SKILL.md +313 -0
  204. package/presets/angular-express/skills/onna-design-system/SKILL.md +615 -0
  205. package/presets/base/preset.yaml +27 -0
  206. package/presets/base/rules/core.md +35 -0
  207. package/presets/generic-ts/preset.yaml +46 -0
  208. package/presets/python/preset.yaml +47 -0
  209. package/presets/python/rules/python.md +10 -0
  210. package/schema/engine.schema.json +73 -0
  211. package/schema/manifest.schema.json +140 -0
  212. package/schema/preset.schema.json +98 -0
  213. package/schema/state.schema.json +27 -0
  214. package/skills/shared/ab-testing/SKILL.md +159 -0
  215. package/skills/shared/ab-testing/evals/README.md +8 -0
  216. package/skills/shared/ab-testing/evals/cases.yaml +49 -0
  217. package/skills/shared/ab-testing/references/pitfalls.md +74 -0
  218. package/skills/shared/ab-testing/references/sample-size-and-cuped.md +128 -0
  219. package/skills/shared/ab-testing/scripts/verify.sh +89 -0
  220. package/skills/shared/accessibility/SKILL.md +216 -0
  221. package/skills/shared/accessibility/evals/README.md +3 -0
  222. package/skills/shared/accessibility/evals/cases.yaml +47 -0
  223. package/skills/shared/accessibility/references/aria-patterns.md +113 -0
  224. package/skills/shared/accessibility/references/wcag22-checklist.md +83 -0
  225. package/skills/shared/accessibility/scripts/verify.sh +103 -0
  226. package/skills/shared/agent-eval/SKILL.md +187 -0
  227. package/skills/shared/agent-eval/evals/README.md +12 -0
  228. package/skills/shared/agent-eval/evals/cases.yaml +45 -0
  229. package/skills/shared/agent-eval/references/judge-design.md +118 -0
  230. package/skills/shared/agent-eval/references/runner-and-gate.md +183 -0
  231. package/skills/shared/agent-eval/scripts/verify.sh +161 -0
  232. package/skills/shared/agent-safety/SKILL.md +169 -0
  233. package/skills/shared/agent-safety/evals/README.md +12 -0
  234. package/skills/shared/agent-safety/evals/cases.yaml +46 -0
  235. package/skills/shared/agent-safety/references/threat-model.md +51 -0
  236. package/skills/shared/analyze/SKILL.md +123 -0
  237. package/skills/shared/analyze/evals/README.md +72 -0
  238. package/skills/shared/analyze/evals/cases.yaml +74 -0
  239. package/skills/shared/angular/SKILL.md +275 -0
  240. package/skills/shared/angular/evals/README.md +3 -0
  241. package/skills/shared/angular/evals/cases.yaml +38 -0
  242. package/skills/shared/angular/references/migration.md +81 -0
  243. package/skills/shared/angular/references/signals-rxjs.md +92 -0
  244. package/skills/shared/angular/scripts/verify.sh +122 -0
  245. package/skills/shared/api-connector-builder/SKILL.md +268 -0
  246. package/skills/shared/api-connector-builder/evals/README.md +11 -0
  247. package/skills/shared/api-connector-builder/evals/cases.yaml +47 -0
  248. package/skills/shared/api-connector-builder/references/auth-flows.md +132 -0
  249. package/skills/shared/api-connector-builder/references/pagination.md +144 -0
  250. package/skills/shared/api-connector-builder/scripts/verify.sh +172 -0
  251. package/skills/shared/api-design/SKILL.md +182 -0
  252. package/skills/shared/api-design/evals/README.md +3 -0
  253. package/skills/shared/api-design/evals/cases.yaml +45 -0
  254. package/skills/shared/api-design/references/graphql-design.md +70 -0
  255. package/skills/shared/api-design/references/openapi-contract.md +86 -0
  256. package/skills/shared/api-design/references/rest-conventions.md +63 -0
  257. package/skills/shared/api-design/references/versioning-and-evolution.md +49 -0
  258. package/skills/shared/api-design/scripts/verify.sh +138 -0
  259. package/skills/shared/astro/SKILL.md +257 -0
  260. package/skills/shared/astro/evals/README.md +3 -0
  261. package/skills/shared/astro/evals/cases.yaml +41 -0
  262. package/skills/shared/astro/references/content-layer.md +118 -0
  263. package/skills/shared/astro/references/deploy-and-integrations.md +163 -0
  264. package/skills/shared/astro/scripts/verify.sh +137 -0
  265. package/skills/shared/author-skill/SKILL.md +194 -0
  266. package/skills/shared/author-skill/evals/README.md +66 -0
  267. package/skills/shared/author-skill/evals/cases.yaml +75 -0
  268. package/skills/shared/author-skill/references/description-recipe.md +85 -0
  269. package/skills/shared/author-skill/references/eval-authoring.md +108 -0
  270. package/skills/shared/author-skill/references/skill-conventions.md +95 -0
  271. package/skills/shared/automation-flows/SKILL.md +122 -0
  272. package/skills/shared/automation-flows/evals/README.md +5 -0
  273. package/skills/shared/automation-flows/evals/cases.yaml +44 -0
  274. package/skills/shared/automation-flows/references/error-handling.md +58 -0
  275. package/skills/shared/automation-flows/references/n8n-workflow-json.md +63 -0
  276. package/skills/shared/automation-flows/scripts/verify.sh +78 -0
  277. package/skills/shared/aws-essentials/SKILL.md +206 -0
  278. package/skills/shared/aws-essentials/evals/README.md +10 -0
  279. package/skills/shared/aws-essentials/evals/cases.yaml +44 -0
  280. package/skills/shared/aws-essentials/references/iam-least-privilege.md +134 -0
  281. package/skills/shared/aws-essentials/references/rds-cloudfront-recipes.md +127 -0
  282. package/skills/shared/aws-essentials/scripts/verify.sh +99 -0
  283. package/skills/shared/backups/SKILL.md +131 -0
  284. package/skills/shared/backups/evals/README.md +3 -0
  285. package/skills/shared/backups/evals/cases.yaml +42 -0
  286. package/skills/shared/backups/references/engine-recipes.md +121 -0
  287. package/skills/shared/backups/references/restore-runbook.md +65 -0
  288. package/skills/shared/backups/scripts/verify.sh +80 -0
  289. package/skills/shared/bash-scripting/SKILL.md +227 -0
  290. package/skills/shared/bash-scripting/evals/README.md +3 -0
  291. package/skills/shared/bash-scripting/evals/cases.yaml +45 -0
  292. package/skills/shared/bash-scripting/references/portability.md +97 -0
  293. package/skills/shared/bash-scripting/scripts/verify.sh +140 -0
  294. package/skills/shared/building-agents/SKILL.md +386 -0
  295. package/skills/shared/building-agents/evals/README.md +68 -0
  296. package/skills/shared/building-agents/evals/cases.yaml +60 -0
  297. package/skills/shared/building-agents/references/agent-loops-and-harness.md +371 -0
  298. package/skills/shared/building-agents/references/evals-and-observability.md +420 -0
  299. package/skills/shared/building-agents/references/mcp-servers.md +294 -0
  300. package/skills/shared/building-agents/references/provider-abstraction.md +489 -0
  301. package/skills/shared/building-agents/references/tools-and-rag.md +417 -0
  302. package/skills/shared/building-agents/scripts/verify.sh +121 -0
  303. package/skills/shared/chatbot/SKILL.md +151 -0
  304. package/skills/shared/chatbot/evals/README.md +13 -0
  305. package/skills/shared/chatbot/evals/cases.yaml +43 -0
  306. package/skills/shared/chatbot/references/handoff-and-sales.md +71 -0
  307. package/skills/shared/chatbot/references/system-prompt-and-guardrails.md +78 -0
  308. package/skills/shared/chatbot/scripts/verify.sh +162 -0
  309. package/skills/shared/chrome-extension/SKILL.md +169 -0
  310. package/skills/shared/chrome-extension/evals/README.md +12 -0
  311. package/skills/shared/chrome-extension/evals/cases.yaml +40 -0
  312. package/skills/shared/chrome-extension/references/store-and-migration.md +84 -0
  313. package/skills/shared/chrome-extension/scripts/verify.sh +62 -0
  314. package/skills/shared/clarify/SKILL.md +161 -0
  315. package/skills/shared/clarify/evals/README.md +70 -0
  316. package/skills/shared/clarify/evals/cases.yaml +71 -0
  317. package/skills/shared/clickhouse-analytics/SKILL.md +159 -0
  318. package/skills/shared/clickhouse-analytics/evals/README.md +3 -0
  319. package/skills/shared/clickhouse-analytics/evals/cases.yaml +45 -0
  320. package/skills/shared/clickhouse-analytics/references/ingestion-and-mvs.md +109 -0
  321. package/skills/shared/clickhouse-analytics/references/query-optimization.md +76 -0
  322. package/skills/shared/clickhouse-analytics/references/schema-and-engines.md +63 -0
  323. package/skills/shared/clickhouse-analytics/scripts/verify.sh +109 -0
  324. package/skills/shared/cloudflare/SKILL.md +186 -0
  325. package/skills/shared/cloudflare/evals/README.md +15 -0
  326. package/skills/shared/cloudflare/evals/cases.yaml +46 -0
  327. package/skills/shared/cloudflare/references/storage-primitives.md +104 -0
  328. package/skills/shared/cloudflare/references/wrangler-config.md +91 -0
  329. package/skills/shared/cloudflare/scripts/verify.sh +133 -0
  330. package/skills/shared/code-review/SKILL.md +132 -0
  331. package/skills/shared/code-review/evals/README.md +3 -0
  332. package/skills/shared/code-review/evals/cases.yaml +55 -0
  333. package/skills/shared/code-review/references/pr-workflow.md +67 -0
  334. package/skills/shared/codebase-onboarding/SKILL.md +128 -0
  335. package/skills/shared/codebase-onboarding/evals/README.md +3 -0
  336. package/skills/shared/codebase-onboarding/evals/cases.yaml +69 -0
  337. package/skills/shared/codebase-onboarding/references/recon-playbook.md +57 -0
  338. package/skills/shared/codebase-onboarding/scripts/verify.sh +54 -0
  339. package/skills/shared/compose-multiplatform/SKILL.md +198 -0
  340. package/skills/shared/compose-multiplatform/evals/README.md +3 -0
  341. package/skills/shared/compose-multiplatform/evals/cases.yaml +40 -0
  342. package/skills/shared/compose-multiplatform/references/ios-interop.md +91 -0
  343. package/skills/shared/compose-multiplatform/references/project-setup.md +96 -0
  344. package/skills/shared/compose-multiplatform/scripts/verify.sh +123 -0
  345. package/skills/shared/connect-tool/SKILL.md +163 -0
  346. package/skills/shared/connect-tool/evals/cases.yaml +58 -0
  347. package/skills/shared/constitution/SKILL.md +158 -0
  348. package/skills/shared/constitution/evals/README.md +68 -0
  349. package/skills/shared/constitution/evals/cases.yaml +72 -0
  350. package/skills/shared/constitution/references/constitution-template.md +100 -0
  351. package/skills/shared/context-budget/SKILL.md +114 -0
  352. package/skills/shared/context-budget/evals/README.md +11 -0
  353. package/skills/shared/context-budget/evals/cases.yaml +40 -0
  354. package/skills/shared/context-budget/references/handoff-and-compaction.md +96 -0
  355. package/skills/shared/continuous-learning/SKILL.md +106 -0
  356. package/skills/shared/continuous-learning/evals/README.md +16 -0
  357. package/skills/shared/continuous-learning/evals/cases.yaml +39 -0
  358. package/skills/shared/continuous-learning/references/lesson-routing.md +106 -0
  359. package/skills/shared/coolify/SKILL.md +194 -0
  360. package/skills/shared/coolify/evals/README.md +21 -0
  361. package/skills/shared/coolify/evals/cases.yaml +46 -0
  362. package/skills/shared/coolify/references/databases-and-backups.md +99 -0
  363. package/skills/shared/coolify/references/deploy-recipes.md +105 -0
  364. package/skills/shared/coolify/references/install-and-proxy.md +80 -0
  365. package/skills/shared/coolify/scripts/verify.sh +123 -0
  366. package/skills/shared/cost-tracking/SKILL.md +176 -0
  367. package/skills/shared/cost-tracking/evals/README.md +3 -0
  368. package/skills/shared/cost-tracking/evals/cases.yaml +45 -0
  369. package/skills/shared/cost-tracking/references/cloud-caps.md +52 -0
  370. package/skills/shared/cost-tracking/references/pricing-tables.md +51 -0
  371. package/skills/shared/cost-tracking/scripts/verify.sh +135 -0
  372. package/skills/shared/cpp/SKILL.md +302 -0
  373. package/skills/shared/cpp/evals/README.md +14 -0
  374. package/skills/shared/cpp/evals/cases.yaml +44 -0
  375. package/skills/shared/cpp/references/cmake.md +167 -0
  376. package/skills/shared/cpp/references/move-and-templates.md +130 -0
  377. package/skills/shared/cpp/references/undefined-behavior.md +86 -0
  378. package/skills/shared/cpp/scripts/verify.sh +165 -0
  379. package/skills/shared/csharp-dotnet/SKILL.md +272 -0
  380. package/skills/shared/csharp-dotnet/evals/README.md +3 -0
  381. package/skills/shared/csharp-dotnet/evals/cases.yaml +48 -0
  382. package/skills/shared/csharp-dotnet/references/aspnetcore.md +99 -0
  383. package/skills/shared/csharp-dotnet/references/async.md +82 -0
  384. package/skills/shared/csharp-dotnet/references/efcore.md +96 -0
  385. package/skills/shared/csharp-dotnet/scripts/verify.sh +90 -0
  386. package/skills/shared/data-cleaning/SKILL.md +259 -0
  387. package/skills/shared/data-cleaning/evals/README.md +16 -0
  388. package/skills/shared/data-cleaning/evals/cases.yaml +57 -0
  389. package/skills/shared/data-cleaning/references/normalization-recipes.md +136 -0
  390. package/skills/shared/data-cleaning/references/validation-patterns.md +134 -0
  391. package/skills/shared/data-cleaning/scripts/verify.sh +115 -0
  392. package/skills/shared/data-scraper/SKILL.md +130 -0
  393. package/skills/shared/data-scraper/evals/README.md +3 -0
  394. package/skills/shared/data-scraper/evals/cases.yaml +46 -0
  395. package/skills/shared/data-scraper/references/anti-bot.md +85 -0
  396. package/skills/shared/data-scraper/references/frameworks.md +116 -0
  397. package/skills/shared/data-scraper/references/legal-compliance.md +64 -0
  398. package/skills/shared/data-scraper/scripts/verify.sh +166 -0
  399. package/skills/shared/db-migrations/SKILL.md +237 -0
  400. package/skills/shared/db-migrations/evals/README.md +10 -0
  401. package/skills/shared/db-migrations/evals/cases.yaml +46 -0
  402. package/skills/shared/db-migrations/references/backfill-and-batching.md +105 -0
  403. package/skills/shared/db-migrations/references/expand-contract-playbook.md +152 -0
  404. package/skills/shared/db-migrations/references/tools-and-runners.md +88 -0
  405. package/skills/shared/db-migrations/scripts/verify.sh +112 -0
  406. package/skills/shared/debug/SKILL.md +188 -0
  407. package/skills/shared/debug/evals/README.md +88 -0
  408. package/skills/shared/debug/evals/cases.yaml +74 -0
  409. package/skills/shared/decision-challenge/SKILL.md +113 -0
  410. package/skills/shared/decision-challenge/evals/README.md +3 -0
  411. package/skills/shared/decision-challenge/evals/cases.yaml +42 -0
  412. package/skills/shared/decision-records/SKILL.md +202 -0
  413. package/skills/shared/decision-records/evals/README.md +3 -0
  414. package/skills/shared/decision-records/evals/cases.yaml +43 -0
  415. package/skills/shared/decision-records/references/templates.md +252 -0
  416. package/skills/shared/decision-records/scripts/verify.sh +105 -0
  417. package/skills/shared/deep-learning/SKILL.md +226 -0
  418. package/skills/shared/deep-learning/evals/README.md +11 -0
  419. package/skills/shared/deep-learning/evals/cases.yaml +48 -0
  420. package/skills/shared/deep-learning/references/distributed.md +105 -0
  421. package/skills/shared/deep-learning/references/training-recipes.md +135 -0
  422. package/skills/shared/deployment/SKILL.md +374 -0
  423. package/skills/shared/deployment/evals/README.md +50 -0
  424. package/skills/shared/deployment/evals/cases.yaml +53 -0
  425. package/skills/shared/deployment/references/coolify.md +216 -0
  426. package/skills/shared/deployment/references/dockerfiles-by-stack.md +319 -0
  427. package/skills/shared/deployment/references/github-actions.md +295 -0
  428. package/skills/shared/deployment/references/hosting-targets.md +272 -0
  429. package/skills/shared/deployment/scripts/verify.sh +134 -0
  430. package/skills/shared/deprecation/SKILL.md +123 -0
  431. package/skills/shared/deprecation/evals/README.md +3 -0
  432. package/skills/shared/deprecation/evals/cases.yaml +42 -0
  433. package/skills/shared/design/IMPECCABLE-LICENSE.txt +191 -0
  434. package/skills/shared/design/SKILL.md +111 -0
  435. package/skills/shared/design/THIRD_PARTY.md +26 -0
  436. package/skills/shared/design/engine/LICENSE +21 -0
  437. package/skills/shared/design/engine/RUN.md +515 -0
  438. package/skills/shared/design/engine/UPSTREAM.json +4 -0
  439. package/skills/shared/design/engine/assets/app.css +216 -0
  440. package/skills/shared/design/engine/assets/base.css +105 -0
  441. package/skills/shared/design/engine/assets/base.js +37 -0
  442. package/skills/shared/design/engine/assets/charts.js +617 -0
  443. package/skills/shared/design/engine/assets/scenes/stage.css +28 -0
  444. package/skills/shared/design/engine/assets/scenes/stage.js +133 -0
  445. package/skills/shared/design/engine/library/app.json +1327 -0
  446. package/skills/shared/design/engine/library/directions.json +1078 -0
  447. package/skills/shared/design/engine/library/heroes.json +1086 -0
  448. package/skills/shared/design/engine/library/motions.json +92 -0
  449. package/skills/shared/design/engine/library/palettes.json +1485 -0
  450. package/skills/shared/design/engine/library/photos.json +7240 -0
  451. package/skills/shared/design/engine/library/sections.json +4429 -0
  452. package/skills/shared/design/engine/library/sites.json +8350 -0
  453. package/skills/shared/design/engine/library/type.json +2024 -0
  454. package/skills/shared/design/engine/references/app-screens.md +1199 -0
  455. package/skills/shared/design/engine/references/colour.md +141 -0
  456. package/skills/shared/design/engine/references/copy.md +177 -0
  457. package/skills/shared/design/engine/references/critique.md +182 -0
  458. package/skills/shared/design/engine/references/imagery.md +791 -0
  459. package/skills/shared/design/engine/references/layout.md +292 -0
  460. package/skills/shared/design/engine/references/motion.md +980 -0
  461. package/skills/shared/design/engine/references/research.md +356 -0
  462. package/skills/shared/design/engine/references/scenes.md +174 -0
  463. package/skills/shared/design/engine/references/tells.md +115 -0
  464. package/skills/shared/design/engine/references/type.md +170 -0
  465. package/skills/shared/design/engine/scripts/assets.mjs +865 -0
  466. package/skills/shared/design/engine/scripts/draw.mjs +584 -0
  467. package/skills/shared/design/engine/scripts/inspect.mjs +303 -0
  468. package/skills/shared/design/engine/scripts/lib/app-draw.mjs +203 -0
  469. package/skills/shared/design/engine/scripts/lib/cdp.mjs +248 -0
  470. package/skills/shared/design/engine/scripts/lib/probe.mjs +909 -0
  471. package/skills/shared/design/engine/scripts/lib/rules.mjs +243 -0
  472. package/skills/shared/design/engine/scripts/lint.mjs +119 -0
  473. package/skills/shared/design/engine/scripts/scenes.mjs +124 -0
  474. package/skills/shared/design/evals/cases.yaml +45 -0
  475. package/skills/shared/design/references/design-md.md +49 -0
  476. package/skills/shared/design/references/finish-review.md +46 -0
  477. package/skills/shared/design/references/modes.md +51 -0
  478. package/skills/shared/digitalocean/SKILL.md +227 -0
  479. package/skills/shared/digitalocean/evals/README.md +10 -0
  480. package/skills/shared/digitalocean/evals/cases.yaml +37 -0
  481. package/skills/shared/digitalocean/references/app-spec.md +126 -0
  482. package/skills/shared/digitalocean/references/droplet-ops.md +95 -0
  483. package/skills/shared/digitalocean/scripts/verify.sh +102 -0
  484. package/skills/shared/django/SKILL.md +263 -0
  485. package/skills/shared/django/evals/README.md +11 -0
  486. package/skills/shared/django/evals/cases.yaml +47 -0
  487. package/skills/shared/django/references/drf.md +109 -0
  488. package/skills/shared/django/references/orm-performance.md +91 -0
  489. package/skills/shared/django/references/security.md +81 -0
  490. package/skills/shared/django/references/testing.md +86 -0
  491. package/skills/shared/django/scripts/verify.sh +115 -0
  492. package/skills/shared/docker/SKILL.md +251 -0
  493. package/skills/shared/docker/evals/README.md +10 -0
  494. package/skills/shared/docker/evals/cases.yaml +44 -0
  495. package/skills/shared/docker/references/base-images-and-stages.md +104 -0
  496. package/skills/shared/docker/references/compose-recipes.md +109 -0
  497. package/skills/shared/docker/scripts/verify.sh +149 -0
  498. package/skills/shared/domains-dns/SKILL.md +131 -0
  499. package/skills/shared/domains-dns/evals/README.md +16 -0
  500. package/skills/shared/domains-dns/evals/cases.yaml +47 -0
  501. package/skills/shared/domains-dns/references/record-cookbook.md +94 -0
  502. package/skills/shared/domains-dns/references/tls-and-acme.md +90 -0
  503. package/skills/shared/domains-dns/references/verify-and-debug.md +64 -0
  504. package/skills/shared/domains-dns/scripts/verify.sh +163 -0
  505. package/skills/shared/drizzle-orm/SKILL.md +231 -0
  506. package/skills/shared/drizzle-orm/evals/README.md +12 -0
  507. package/skills/shared/drizzle-orm/evals/cases.yaml +47 -0
  508. package/skills/shared/drizzle-orm/references/relations-and-drivers.md +118 -0
  509. package/skills/shared/drizzle-orm/scripts/verify.sh +155 -0
  510. package/skills/shared/duckdb/SKILL.md +202 -0
  511. package/skills/shared/duckdb/evals/README.md +31 -0
  512. package/skills/shared/duckdb/evals/cases.yaml +41 -0
  513. package/skills/shared/duckdb/references/python-and-interop.md +105 -0
  514. package/skills/shared/duckdb/references/remote-and-lakehouse.md +101 -0
  515. package/skills/shared/duckdb/scripts/verify.sh +71 -0
  516. package/skills/shared/dynamodb/SKILL.md +204 -0
  517. package/skills/shared/dynamodb/evals/README.md +8 -0
  518. package/skills/shared/dynamodb/evals/cases.yaml +46 -0
  519. package/skills/shared/dynamodb/references/access-patterns.md +127 -0
  520. package/skills/shared/dynamodb/references/capacity-and-limits.md +78 -0
  521. package/skills/shared/dynamodb/scripts/verify.sh +108 -0
  522. package/skills/shared/e2e-testing/SKILL.md +233 -0
  523. package/skills/shared/e2e-testing/evals/README.md +10 -0
  524. package/skills/shared/e2e-testing/evals/cases.yaml +64 -0
  525. package/skills/shared/e2e-testing/references/config-and-ci.md +156 -0
  526. package/skills/shared/e2e-testing/references/flakiness-playbook.md +124 -0
  527. package/skills/shared/e2e-testing/scripts/verify.sh +117 -0
  528. package/skills/shared/electron/SKILL.md +209 -0
  529. package/skills/shared/electron/evals/README.md +13 -0
  530. package/skills/shared/electron/evals/cases.yaml +38 -0
  531. package/skills/shared/electron/references/packaging-and-updates.md +122 -0
  532. package/skills/shared/electron/references/security-and-ipc.md +158 -0
  533. package/skills/shared/electron/scripts/verify.sh +143 -0
  534. package/skills/shared/elixir/SKILL.md +209 -0
  535. package/skills/shared/elixir/evals/README.md +3 -0
  536. package/skills/shared/elixir/evals/cases.yaml +41 -0
  537. package/skills/shared/elixir/references/mix-and-releases.md +91 -0
  538. package/skills/shared/elixir/references/otp-patterns.md +96 -0
  539. package/skills/shared/elixir/scripts/verify.sh +76 -0
  540. package/skills/shared/email-deliverability/SKILL.md +148 -0
  541. package/skills/shared/email-deliverability/evals/README.md +21 -0
  542. package/skills/shared/email-deliverability/evals/cases.yaml +45 -0
  543. package/skills/shared/email-deliverability/scripts/verify.sh +98 -0
  544. package/skills/shared/embeddings-search/SKILL.md +184 -0
  545. package/skills/shared/embeddings-search/evals/README.md +10 -0
  546. package/skills/shared/embeddings-search/evals/cases.yaml +44 -0
  547. package/skills/shared/embeddings-search/references/evaluation.md +86 -0
  548. package/skills/shared/embeddings-search/references/models.md +73 -0
  549. package/skills/shared/embeddings-search/scripts/verify.sh +103 -0
  550. package/skills/shared/error-handling/SKILL.md +273 -0
  551. package/skills/shared/error-handling/evals/README.md +12 -0
  552. package/skills/shared/error-handling/evals/cases.yaml +46 -0
  553. package/skills/shared/error-handling/references/boundaries-and-messaging.md +120 -0
  554. package/skills/shared/error-handling/references/retry-and-resilience.md +154 -0
  555. package/skills/shared/error-handling/scripts/verify.sh +110 -0
  556. package/skills/shared/expo/SKILL.md +243 -0
  557. package/skills/shared/expo/evals/README.md +13 -0
  558. package/skills/shared/expo/evals/cases.yaml +44 -0
  559. package/skills/shared/expo/references/config-plugins.md +117 -0
  560. package/skills/shared/expo/references/eas-update.md +118 -0
  561. package/skills/shared/expo/scripts/verify.sh +132 -0
  562. package/skills/shared/fal/SKILL.md +196 -0
  563. package/skills/shared/fal/evals/README.md +3 -0
  564. package/skills/shared/fal/evals/cases.yaml +42 -0
  565. package/skills/shared/fal/references/models-and-cost.md +53 -0
  566. package/skills/shared/fal/references/queue-and-webhooks.md +153 -0
  567. package/skills/shared/fal/scripts/verify.sh +72 -0
  568. package/skills/shared/fastapi/SKILL.md +469 -0
  569. package/skills/shared/fastapi/evals/README.md +50 -0
  570. package/skills/shared/fastapi/evals/cases.yaml +55 -0
  571. package/skills/shared/fastapi/references/database.md +347 -0
  572. package/skills/shared/fastapi/references/production.md +338 -0
  573. package/skills/shared/fastapi/references/security.md +330 -0
  574. package/skills/shared/fastapi/references/testing.md +349 -0
  575. package/skills/shared/fastapi/scripts/verify.sh +157 -0
  576. package/skills/shared/finetuning/SKILL.md +232 -0
  577. package/skills/shared/finetuning/evals/README.md +13 -0
  578. package/skills/shared/finetuning/evals/cases.yaml +50 -0
  579. package/skills/shared/finetuning/references/hyperparameters-and-eval.md +74 -0
  580. package/skills/shared/finetuning/references/methods.md +189 -0
  581. package/skills/shared/firebase/SKILL.md +237 -0
  582. package/skills/shared/firebase/evals/README.md +12 -0
  583. package/skills/shared/firebase/evals/cases.yaml +45 -0
  584. package/skills/shared/firebase/references/cloud-functions.md +102 -0
  585. package/skills/shared/firebase/references/data-modeling.md +108 -0
  586. package/skills/shared/firebase/references/security-rules.md +137 -0
  587. package/skills/shared/firebase/scripts/verify.sh +98 -0
  588. package/skills/shared/flutter/SKILL.md +400 -0
  589. package/skills/shared/flutter/evals/README.md +54 -0
  590. package/skills/shared/flutter/evals/cases.yaml +69 -0
  591. package/skills/shared/flutter/references/architecture-and-state.md +499 -0
  592. package/skills/shared/flutter/references/i18n-and-dependencies.md +197 -0
  593. package/skills/shared/flutter/references/performance.md +299 -0
  594. package/skills/shared/flutter/references/testing.md +385 -0
  595. package/skills/shared/flutter/references/ui-and-navigation.md +378 -0
  596. package/skills/shared/flutter/scripts/verify.sh +104 -0
  597. package/skills/shared/fly-io/SKILL.md +197 -0
  598. package/skills/shared/fly-io/evals/README.md +3 -0
  599. package/skills/shared/fly-io/evals/cases.yaml +42 -0
  600. package/skills/shared/fly-io/references/fly-toml.md +155 -0
  601. package/skills/shared/fly-io/references/multi-region.md +66 -0
  602. package/skills/shared/fly-io/scripts/verify.sh +90 -0
  603. package/skills/shared/ftd/SKILL.md +111 -0
  604. package/skills/shared/ftd/evals/cases.yaml +66 -0
  605. package/skills/shared/ftd/evals/routing.json +24 -0
  606. package/skills/shared/game-design/SKILL.md +146 -0
  607. package/skills/shared/game-design/evals/README.md +13 -0
  608. package/skills/shared/game-design/evals/cases.yaml +47 -0
  609. package/skills/shared/game-design/references/mechanics-and-loops.md +78 -0
  610. package/skills/shared/game-design/references/playtesting-and-scope.md +58 -0
  611. package/skills/shared/game-design/references/progression-and-economy.md +70 -0
  612. package/skills/shared/game-storytelling/SKILL.md +134 -0
  613. package/skills/shared/game-storytelling/evals/README.md +28 -0
  614. package/skills/shared/game-storytelling/evals/cases.yaml +51 -0
  615. package/skills/shared/game-storytelling/references/branching-and-state.md +134 -0
  616. package/skills/shared/game-storytelling/references/dialogue-tools.md +117 -0
  617. package/skills/shared/game-storytelling/references/environmental-storytelling.md +71 -0
  618. package/skills/shared/gamedev-multiplayer/SKILL.md +237 -0
  619. package/skills/shared/gamedev-multiplayer/evals/README.md +12 -0
  620. package/skills/shared/gamedev-multiplayer/evals/cases.yaml +44 -0
  621. package/skills/shared/gamedev-multiplayer/references/godot-high-level-multiplayer.md +133 -0
  622. package/skills/shared/gamedev-multiplayer/references/prediction-and-latency.md +104 -0
  623. package/skills/shared/gamedev-multiplayer/references/unity-and-unreal-netcode.md +166 -0
  624. package/skills/shared/gamedev-pathing/SKILL.md +233 -0
  625. package/skills/shared/gamedev-pathing/evals/README.md +14 -0
  626. package/skills/shared/gamedev-pathing/evals/cases.yaml +49 -0
  627. package/skills/shared/gamedev-pathing/references/navmesh-workflow.md +110 -0
  628. package/skills/shared/gamedev-pathing/references/search-algorithms.md +135 -0
  629. package/skills/shared/gamedev-pathing/references/steering-and-avoidance.md +120 -0
  630. package/skills/shared/gamedev-physics/SKILL.md +225 -0
  631. package/skills/shared/gamedev-physics/evals/README.md +12 -0
  632. package/skills/shared/gamedev-physics/evals/cases.yaml +54 -0
  633. package/skills/shared/gamedev-physics/references/character-controllers.md +120 -0
  634. package/skills/shared/gamedev-physics/references/determinism-and-queries.md +124 -0
  635. package/skills/shared/gamedev-physics/references/layers-and-masks.md +115 -0
  636. package/skills/shared/gamedev-shaders/SKILL.md +232 -0
  637. package/skills/shared/gamedev-shaders/evals/README.md +14 -0
  638. package/skills/shared/gamedev-shaders/evals/cases.yaml +45 -0
  639. package/skills/shared/gamedev-shaders/references/effect-recipes.md +205 -0
  640. package/skills/shared/gamedev-shaders/references/godot-shading-language.md +133 -0
  641. package/skills/shared/gamedev-shaders/references/unity-and-unreal-shaders.md +169 -0
  642. package/skills/shared/gamedev-shipping/SKILL.md +220 -0
  643. package/skills/shared/gamedev-shipping/evals/README.md +9 -0
  644. package/skills/shared/gamedev-shipping/evals/cases.yaml +55 -0
  645. package/skills/shared/gamedev-shipping/references/mobile-and-desktop.md +127 -0
  646. package/skills/shared/gamedev-shipping/references/stores-and-ci.md +162 -0
  647. package/skills/shared/gamedev-shipping/references/web-export-headers.md +140 -0
  648. package/skills/shared/gcp-essentials/SKILL.md +324 -0
  649. package/skills/shared/gcp-essentials/evals/README.md +12 -0
  650. package/skills/shared/gcp-essentials/evals/cases.yaml +38 -0
  651. package/skills/shared/gcp-essentials/references/deploy-recipes.md +81 -0
  652. package/skills/shared/gcp-essentials/references/iam-and-auth.md +94 -0
  653. package/skills/shared/gcp-essentials/references/networking-and-sql.md +74 -0
  654. package/skills/shared/gcp-essentials/scripts/verify.sh +158 -0
  655. package/skills/shared/git-workflow/SKILL.md +214 -0
  656. package/skills/shared/git-workflow/evals/README.md +10 -0
  657. package/skills/shared/git-workflow/evals/cases.yaml +52 -0
  658. package/skills/shared/git-workflow/references/gitmoji.md +157 -0
  659. package/skills/shared/git-workflow/references/interactive-rebase.md +89 -0
  660. package/skills/shared/github-actions/SKILL.md +244 -0
  661. package/skills/shared/github-actions/evals/README.md +3 -0
  662. package/skills/shared/github-actions/evals/cases.yaml +45 -0
  663. package/skills/shared/github-actions/references/caching-and-matrix.md +92 -0
  664. package/skills/shared/github-actions/references/oidc-deploys.md +130 -0
  665. package/skills/shared/github-actions/scripts/verify.sh +105 -0
  666. package/skills/shared/gitlab-auth/SKILL.md +99 -0
  667. package/skills/shared/gitlab-workflow/SKILL.md +138 -0
  668. package/skills/shared/go/SKILL.md +390 -0
  669. package/skills/shared/go/evals/README.md +56 -0
  670. package/skills/shared/go/evals/cases.yaml +55 -0
  671. package/skills/shared/go/references/concurrency.md +557 -0
  672. package/skills/shared/go/references/http-services.md +529 -0
  673. package/skills/shared/go/references/testing.md +338 -0
  674. package/skills/shared/go/scripts/verify.sh +134 -0
  675. package/skills/shared/godot/SKILL.md +300 -0
  676. package/skills/shared/godot/evals/README.md +13 -0
  677. package/skills/shared/godot/evals/cases.yaml +50 -0
  678. package/skills/shared/godot/references/export-and-testing.md +102 -0
  679. package/skills/shared/godot/references/gdscript-style.md +84 -0
  680. package/skills/shared/godot/references/godot3-to-4-traps.md +89 -0
  681. package/skills/shared/godot/references/nodes-scenes-resources.md +109 -0
  682. package/skills/shared/harness/SKILL.md +316 -0
  683. package/skills/shared/harness/assets/_TEMPLATE/.env.example +8 -0
  684. package/skills/shared/harness/assets/_TEMPLATE/CREDENTIALS.md +25 -0
  685. package/skills/shared/harness/assets/_TEMPLATE/README.md +25 -0
  686. package/skills/shared/harness/assets/_TEMPLATE/gitignore +10 -0
  687. package/skills/shared/harness/assets/_TEMPLATE/test_connection.sh +30 -0
  688. package/skills/shared/harness/evals/README.md +54 -0
  689. package/skills/shared/harness/evals/cases.yaml +72 -0
  690. package/skills/shared/harness/examples/audit-example.md +120 -0
  691. package/skills/shared/harness/references/agents-md-template.md +41 -0
  692. package/skills/shared/harness/references/audit-report-template.html +140 -0
  693. package/skills/shared/harness/references/audit-report-template.md +116 -0
  694. package/skills/shared/harness/references/claude-md-template.md +85 -0
  695. package/skills/shared/harness/references/daily-curation-automation.md +68 -0
  696. package/skills/shared/harness/references/inbox-readme-template.md +51 -0
  697. package/skills/shared/harness/references/ingest-formats.md +185 -0
  698. package/skills/shared/harness/references/ingest-ignore-defaults.md +48 -0
  699. package/skills/shared/harness/references/obsidian-scaffolding.md +103 -0
  700. package/skills/shared/harness/references/providers.yaml +3546 -0
  701. package/skills/shared/harness/references/tools-readme-template.md +88 -0
  702. package/skills/shared/harness/references/wiki-archive-template.html +81 -0
  703. package/skills/shared/harness/references/wiki-article-template.md +60 -0
  704. package/skills/shared/harness/references/wiki-dashboard-template.html +136 -0
  705. package/skills/shared/harness/references/wiki-deep-improve-report-template.html +126 -0
  706. package/skills/shared/harness/references/wiki-gaps-template.md +22 -0
  707. package/skills/shared/harness/references/wiki-index-template.md +32 -0
  708. package/skills/shared/harness/references/wiki-protocol.md +926 -0
  709. package/skills/shared/harness/references/wiki-raw-template.md +7 -0
  710. package/skills/shared/harness/references/wiki-worklog-template.md +60 -0
  711. package/skills/shared/hetzner/SKILL.md +203 -0
  712. package/skills/shared/hetzner/evals/README.md +35 -0
  713. package/skills/shared/hetzner/evals/cases.yaml +46 -0
  714. package/skills/shared/hetzner/references/cloud-init.md +120 -0
  715. package/skills/shared/hetzner/references/plans-and-locations.md +56 -0
  716. package/skills/shared/hetzner/scripts/verify.sh +122 -0
  717. package/skills/shared/htmx/SKILL.md +261 -0
  718. package/skills/shared/htmx/evals/README.md +3 -0
  719. package/skills/shared/htmx/evals/cases.yaml +38 -0
  720. package/skills/shared/htmx/references/patterns.md +113 -0
  721. package/skills/shared/htmx/references/server-contract.md +91 -0
  722. package/skills/shared/htmx/scripts/verify.sh +93 -0
  723. package/skills/shared/huggingface/SKILL.md +191 -0
  724. package/skills/shared/huggingface/evals/README.md +11 -0
  725. package/skills/shared/huggingface/evals/cases.yaml +41 -0
  726. package/skills/shared/huggingface/references/endpoints-and-spaces.md +99 -0
  727. package/skills/shared/huggingface/references/hub-and-cli.md +85 -0
  728. package/skills/shared/huggingface/references/inference-providers.md +115 -0
  729. package/skills/shared/huggingface/scripts/verify.sh +123 -0
  730. package/skills/shared/idea-refinement/SKILL.md +121 -0
  731. package/skills/shared/idea-refinement/evals/README.md +3 -0
  732. package/skills/shared/idea-refinement/evals/cases.yaml +42 -0
  733. package/skills/shared/implement/SKILL.md +383 -0
  734. package/skills/shared/implement/evals/README.md +56 -0
  735. package/skills/shared/implement/evals/cases.yaml +43 -0
  736. package/skills/shared/implement/references/per-task-review.md +46 -0
  737. package/skills/shared/implement/scripts/review-package +59 -0
  738. package/skills/shared/implement/scripts/sdd-workspace +47 -0
  739. package/skills/shared/implement/scripts/task-brief +77 -0
  740. package/skills/shared/init/SKILL.md +209 -0
  741. package/skills/shared/init/evals/README.md +49 -0
  742. package/skills/shared/init/evals/cases.yaml +74 -0
  743. package/skills/shared/init/references/accompaniment-and-profile.md +122 -0
  744. package/skills/shared/init/references/discovery.md +90 -0
  745. package/skills/shared/init/references/recommend-skills.md +115 -0
  746. package/skills/shared/init/scripts/verify.sh +118 -0
  747. package/skills/shared/java/SKILL.md +341 -0
  748. package/skills/shared/java/evals/README.md +23 -0
  749. package/skills/shared/java/evals/cases.yaml +43 -0
  750. package/skills/shared/java/references/builds.md +133 -0
  751. package/skills/shared/java/references/concurrency.md +108 -0
  752. package/skills/shared/java/references/streams.md +102 -0
  753. package/skills/shared/java/scripts/verify.sh +107 -0
  754. package/skills/shared/knowledge-ops/SKILL.md +122 -0
  755. package/skills/shared/knowledge-ops/evals/README.md +16 -0
  756. package/skills/shared/knowledge-ops/evals/cases.yaml +50 -0
  757. package/skills/shared/knowledge-ops/references/gardening-playbook.md +126 -0
  758. package/skills/shared/kotlin-android/SKILL.md +245 -0
  759. package/skills/shared/kotlin-android/evals/README.md +13 -0
  760. package/skills/shared/kotlin-android/evals/cases.yaml +56 -0
  761. package/skills/shared/kotlin-android/references/architecture.md +200 -0
  762. package/skills/shared/kotlin-android/references/gradle-setup.md +125 -0
  763. package/skills/shared/kotlin-android/scripts/verify.sh +109 -0
  764. package/skills/shared/laravel/SKILL.md +301 -0
  765. package/skills/shared/laravel/evals/README.md +10 -0
  766. package/skills/shared/laravel/evals/cases.yaml +45 -0
  767. package/skills/shared/laravel/references/eloquent-patterns.md +126 -0
  768. package/skills/shared/laravel/references/queues-and-scheduling.md +153 -0
  769. package/skills/shared/laravel/scripts/verify.sh +128 -0
  770. package/skills/shared/level-design/SKILL.md +143 -0
  771. package/skills/shared/level-design/evals/README.md +35 -0
  772. package/skills/shared/level-design/evals/cases.yaml +58 -0
  773. package/skills/shared/level-design/references/blockout-to-art.md +118 -0
  774. package/skills/shared/level-design/references/guidance-and-readability.md +152 -0
  775. package/skills/shared/level-design/references/pacing-and-encounters.md +142 -0
  776. package/skills/shared/llm-pipeline/SKILL.md +150 -0
  777. package/skills/shared/llm-pipeline/evals/README.md +3 -0
  778. package/skills/shared/llm-pipeline/evals/cases.yaml +44 -0
  779. package/skills/shared/llm-pipeline/references/caching-layers.md +60 -0
  780. package/skills/shared/llm-pipeline/references/litellm-router.md +101 -0
  781. package/skills/shared/llm-pipeline/scripts/verify.sh +169 -0
  782. package/skills/shared/machine-learning/SKILL.md +228 -0
  783. package/skills/shared/machine-learning/evals/README.md +15 -0
  784. package/skills/shared/machine-learning/evals/cases.yaml +54 -0
  785. package/skills/shared/machine-learning/references/gbdt-and-tuning.md +83 -0
  786. package/skills/shared/machine-learning/references/metrics-and-imbalance.md +82 -0
  787. package/skills/shared/machine-learning/references/pipelines-and-cv.md +135 -0
  788. package/skills/shared/modal/SKILL.md +290 -0
  789. package/skills/shared/modal/evals/README.md +29 -0
  790. package/skills/shared/modal/evals/cases.yaml +50 -0
  791. package/skills/shared/modal/references/images-gpu-cookbook.md +160 -0
  792. package/skills/shared/modal/references/web-and-scaling.md +138 -0
  793. package/skills/shared/modal/scripts/verify.sh +127 -0
  794. package/skills/shared/mongodb/SKILL.md +342 -0
  795. package/skills/shared/mongodb/evals/README.md +29 -0
  796. package/skills/shared/mongodb/evals/cases.yaml +41 -0
  797. package/skills/shared/mongodb/references/aggregation.md +115 -0
  798. package/skills/shared/mongodb/references/data-modeling.md +135 -0
  799. package/skills/shared/mongodb/references/transactions-and-ops.md +128 -0
  800. package/skills/shared/mongodb/scripts/verify.sh +151 -0
  801. package/skills/shared/monitoring/SKILL.md +155 -0
  802. package/skills/shared/monitoring/evals/README.md +3 -0
  803. package/skills/shared/monitoring/evals/cases.yaml +47 -0
  804. package/skills/shared/monitoring/references/burn-rate-and-oncall.md +128 -0
  805. package/skills/shared/monitoring/references/tool-setup.md +154 -0
  806. package/skills/shared/monitoring/scripts/verify.sh +145 -0
  807. package/skills/shared/mysql/SKILL.md +249 -0
  808. package/skills/shared/mysql/evals/README.md +12 -0
  809. package/skills/shared/mysql/evals/cases.yaml +49 -0
  810. package/skills/shared/mysql/references/indexing-and-explain.md +161 -0
  811. package/skills/shared/mysql/references/mysql-vs-mariadb.md +78 -0
  812. package/skills/shared/mysql/references/online-ddl-and-migrations.md +120 -0
  813. package/skills/shared/mysql/references/replication-and-ha.md +115 -0
  814. package/skills/shared/mysql/scripts/verify.sh +141 -0
  815. package/skills/shared/neon/SKILL.md +218 -0
  816. package/skills/shared/neon/evals/README.md +11 -0
  817. package/skills/shared/neon/evals/cases.yaml +45 -0
  818. package/skills/shared/neon/references/branching-ci.md +86 -0
  819. package/skills/shared/neon/scripts/verify.sh +78 -0
  820. package/skills/shared/nestjs/SKILL.md +217 -0
  821. package/skills/shared/nestjs/evals/README.md +3 -0
  822. package/skills/shared/nestjs/evals/cases.yaml +38 -0
  823. package/skills/shared/nestjs/references/cross-cutting.md +135 -0
  824. package/skills/shared/nestjs/references/testing-recipes.md +105 -0
  825. package/skills/shared/nestjs/scripts/verify.sh +98 -0
  826. package/skills/shared/netlify/SKILL.md +208 -0
  827. package/skills/shared/netlify/evals/README.md +13 -0
  828. package/skills/shared/netlify/evals/cases.yaml +43 -0
  829. package/skills/shared/netlify/references/functions.md +97 -0
  830. package/skills/shared/netlify/references/netlify-toml.md +115 -0
  831. package/skills/shared/netlify/scripts/verify.sh +95 -0
  832. package/skills/shared/nextjs/SKILL.md +400 -0
  833. package/skills/shared/nextjs/evals/README.md +59 -0
  834. package/skills/shared/nextjs/evals/cases.yaml +56 -0
  835. package/skills/shared/nextjs/references/data-and-caching.md +309 -0
  836. package/skills/shared/nextjs/references/metadata.md +208 -0
  837. package/skills/shared/nextjs/references/performance.md +325 -0
  838. package/skills/shared/nextjs/references/react.md +383 -0
  839. package/skills/shared/nextjs/references/security.md +239 -0
  840. package/skills/shared/nextjs/references/testing.md +290 -0
  841. package/skills/shared/nextjs/scripts/verify.sh +141 -0
  842. package/skills/shared/nlp/SKILL.md +225 -0
  843. package/skills/shared/nlp/evals/README.md +10 -0
  844. package/skills/shared/nlp/evals/cases.yaml +45 -0
  845. package/skills/shared/nlp/references/evaluation.md +107 -0
  846. package/skills/shared/nlp/references/tokenization.md +95 -0
  847. package/skills/shared/no-code-app/SKILL.md +145 -0
  848. package/skills/shared/no-code-app/evals/README.md +3 -0
  849. package/skills/shared/no-code-app/evals/cases.yaml +43 -0
  850. package/skills/shared/no-code-app/references/platform-limits.md +100 -0
  851. package/skills/shared/nodejs/SKILL.md +229 -0
  852. package/skills/shared/nodejs/evals/README.md +3 -0
  853. package/skills/shared/nodejs/evals/cases.yaml +39 -0
  854. package/skills/shared/nodejs/references/express5-migration.md +53 -0
  855. package/skills/shared/nodejs/references/graceful-shutdown.md +73 -0
  856. package/skills/shared/nodejs/scripts/verify.sh +122 -0
  857. package/skills/shared/observability/SKILL.md +235 -0
  858. package/skills/shared/observability/evals/README.md +3 -0
  859. package/skills/shared/observability/evals/cases.yaml +51 -0
  860. package/skills/shared/observability/references/collector-config.md +98 -0
  861. package/skills/shared/observability/references/instrumentation-recipes.md +115 -0
  862. package/skills/shared/observability/scripts/verify.sh +156 -0
  863. package/skills/shared/ollama/SKILL.md +213 -0
  864. package/skills/shared/ollama/evals/README.md +9 -0
  865. package/skills/shared/ollama/evals/cases.yaml +43 -0
  866. package/skills/shared/ollama/references/api.md +148 -0
  867. package/skills/shared/ollama/references/hardware-sizing.md +87 -0
  868. package/skills/shared/ollama/scripts/verify.sh +116 -0
  869. package/skills/shared/open-weights/SKILL.md +239 -0
  870. package/skills/shared/open-weights/evals/README.md +14 -0
  871. package/skills/shared/open-weights/evals/cases.yaml +42 -0
  872. package/skills/shared/open-weights/references/licenses.md +123 -0
  873. package/skills/shared/open-weights/references/model-families.md +83 -0
  874. package/skills/shared/open-weights/references/sizing-and-quant.md +89 -0
  875. package/skills/shared/orient/SKILL.md +92 -0
  876. package/skills/shared/orient/evals/README.md +18 -0
  877. package/skills/shared/orient/evals/cases.yaml +67 -0
  878. package/skills/shared/orient/references/explain-ladder.md +93 -0
  879. package/skills/shared/orient/references/orientation-contract.md +86 -0
  880. package/skills/shared/parallel/SKILL.md +168 -0
  881. package/skills/shared/parallel/evals/README.md +62 -0
  882. package/skills/shared/parallel/evals/cases.yaml +44 -0
  883. package/skills/shared/performance/SKILL.md +229 -0
  884. package/skills/shared/performance/evals/README.md +3 -0
  885. package/skills/shared/performance/evals/cases.yaml +49 -0
  886. package/skills/shared/performance/references/profiling-playbook.md +54 -0
  887. package/skills/shared/performance/scripts/verify.sh +94 -0
  888. package/skills/shared/phoenix/SKILL.md +169 -0
  889. package/skills/shared/phoenix/evals/README.md +3 -0
  890. package/skills/shared/phoenix/evals/cases.yaml +40 -0
  891. package/skills/shared/phoenix/references/auth-and-scopes.md +82 -0
  892. package/skills/shared/phoenix/references/ecto-patterns.md +93 -0
  893. package/skills/shared/phoenix/references/liveview.md +134 -0
  894. package/skills/shared/phoenix/scripts/verify.sh +73 -0
  895. package/skills/shared/php/SKILL.md +397 -0
  896. package/skills/shared/php/evals/README.md +12 -0
  897. package/skills/shared/php/evals/cases.yaml +45 -0
  898. package/skills/shared/php/references/tooling.md +170 -0
  899. package/skills/shared/php/references/type-system.md +220 -0
  900. package/skills/shared/php/scripts/verify.sh +155 -0
  901. package/skills/shared/plan/SKILL.md +166 -0
  902. package/skills/shared/plan/evals/README.md +62 -0
  903. package/skills/shared/plan/evals/cases.yaml +49 -0
  904. package/skills/shared/plan/references/plan-template.md +153 -0
  905. package/skills/shared/planetscale/SKILL.md +198 -0
  906. package/skills/shared/planetscale/evals/README.md +11 -0
  907. package/skills/shared/planetscale/evals/cases.yaml +46 -0
  908. package/skills/shared/planetscale/references/deploy-requests.md +75 -0
  909. package/skills/shared/planetscale/references/no-foreign-keys.md +88 -0
  910. package/skills/shared/planetscale/scripts/verify.sh +115 -0
  911. package/skills/shared/planning-with-files/SKILL.md +278 -0
  912. package/skills/shared/planning-with-files/examples.md +202 -0
  913. package/skills/shared/planning-with-files/reference.md +218 -0
  914. package/skills/shared/planning-with-files/scripts/attest-plan.ps1 +118 -0
  915. package/skills/shared/planning-with-files/scripts/attest-plan.sh +110 -0
  916. package/skills/shared/planning-with-files/scripts/check-complete.ps1 +44 -0
  917. package/skills/shared/planning-with-files/scripts/check-complete.sh +46 -0
  918. package/skills/shared/planning-with-files/scripts/init-session.ps1 +166 -0
  919. package/skills/shared/planning-with-files/scripts/init-session.sh +284 -0
  920. package/skills/shared/planning-with-files/scripts/resolve-plan-dir.ps1 +44 -0
  921. package/skills/shared/planning-with-files/scripts/resolve-plan-dir.sh +76 -0
  922. package/skills/shared/planning-with-files/scripts/session-catchup.py +438 -0
  923. package/skills/shared/planning-with-files/scripts/set-active-plan.ps1 +50 -0
  924. package/skills/shared/planning-with-files/scripts/set-active-plan.sh +50 -0
  925. package/skills/shared/planning-with-files/templates/findings.md +95 -0
  926. package/skills/shared/planning-with-files/templates/progress.md +114 -0
  927. package/skills/shared/planning-with-files/templates/task_plan.md +132 -0
  928. package/skills/shared/postgresdb/SKILL.md +354 -0
  929. package/skills/shared/postgresdb/evals/README.md +55 -0
  930. package/skills/shared/postgresdb/evals/cases.yaml +57 -0
  931. package/skills/shared/postgresdb/references/migrations.md +279 -0
  932. package/skills/shared/postgresdb/references/operations-and-security.md +267 -0
  933. package/skills/shared/postgresdb/references/query-optimization.md +374 -0
  934. package/skills/shared/postgresdb/references/schema-and-indexing.md +379 -0
  935. package/skills/shared/postgresdb/scripts/verify.sh +191 -0
  936. package/skills/shared/prisma-orm/SKILL.md +313 -0
  937. package/skills/shared/prisma-orm/evals/README.md +12 -0
  938. package/skills/shared/prisma-orm/evals/cases.yaml +56 -0
  939. package/skills/shared/prisma-orm/references/migrations-and-v7-upgrade.md +197 -0
  940. package/skills/shared/prisma-orm/references/queries-and-performance.md +169 -0
  941. package/skills/shared/prisma-orm/scripts/verify.sh +137 -0
  942. package/skills/shared/prompt-engineering/SKILL.md +127 -0
  943. package/skills/shared/prompt-engineering/evals/README.md +11 -0
  944. package/skills/shared/prompt-engineering/evals/cases.yaml +46 -0
  945. package/skills/shared/prompt-engineering/references/eval-templates.md +94 -0
  946. package/skills/shared/prompt-engineering/references/output-contracts.md +120 -0
  947. package/skills/shared/prompt-engineering/scripts/verify.sh +84 -0
  948. package/skills/shared/python/SKILL.md +320 -0
  949. package/skills/shared/python/evals/README.md +19 -0
  950. package/skills/shared/python/evals/cases.yaml +46 -0
  951. package/skills/shared/python/references/async.md +136 -0
  952. package/skills/shared/python/references/stdlib.md +162 -0
  953. package/skills/shared/python/references/typing.md +160 -0
  954. package/skills/shared/python/scripts/verify.sh +125 -0
  955. package/skills/shared/rag/SKILL.md +209 -0
  956. package/skills/shared/rag/evals/README.md +13 -0
  957. package/skills/shared/rag/evals/cases.yaml +45 -0
  958. package/skills/shared/rag/references/evaluation.md +99 -0
  959. package/skills/shared/rag/references/pipeline.md +151 -0
  960. package/skills/shared/rag/scripts/verify.sh +99 -0
  961. package/skills/shared/rails/SKILL.md +264 -0
  962. package/skills/shared/rails/evals/README.md +12 -0
  963. package/skills/shared/rails/evals/cases.yaml +47 -0
  964. package/skills/shared/rails/references/activerecord.md +148 -0
  965. package/skills/shared/rails/references/hotwire.md +139 -0
  966. package/skills/shared/rails/references/testing.md +110 -0
  967. package/skills/shared/rails/scripts/verify.sh +128 -0
  968. package/skills/shared/railway/SKILL.md +245 -0
  969. package/skills/shared/railway/evals/README.md +14 -0
  970. package/skills/shared/railway/evals/cases.yaml +44 -0
  971. package/skills/shared/railway/references/cli-cookbook.md +137 -0
  972. package/skills/shared/railway/references/config-as-code.md +120 -0
  973. package/skills/shared/railway/scripts/verify.sh +162 -0
  974. package/skills/shared/react/SKILL.md +182 -0
  975. package/skills/shared/react/evals/README.md +3 -0
  976. package/skills/shared/react/evals/cases.yaml +43 -0
  977. package/skills/shared/react/references/data-and-state.md +152 -0
  978. package/skills/shared/react/references/performance.md +75 -0
  979. package/skills/shared/react/references/routing.md +99 -0
  980. package/skills/shared/react/scripts/verify.sh +123 -0
  981. package/skills/shared/react-native/SKILL.md +215 -0
  982. package/skills/shared/react-native/evals/README.md +3 -0
  983. package/skills/shared/react-native/evals/cases.yaml +42 -0
  984. package/skills/shared/react-native/references/native-modules.md +123 -0
  985. package/skills/shared/react-native/references/performance-debugging.md +46 -0
  986. package/skills/shared/react-native/scripts/verify.sh +120 -0
  987. package/skills/shared/redis/SKILL.md +298 -0
  988. package/skills/shared/redis/evals/README.md +10 -0
  989. package/skills/shared/redis/evals/cases.yaml +43 -0
  990. package/skills/shared/redis/references/caching.md +116 -0
  991. package/skills/shared/redis/references/locks-and-rate-limiting.md +140 -0
  992. package/skills/shared/redis/references/queues.md +102 -0
  993. package/skills/shared/redis/scripts/verify.sh +164 -0
  994. package/skills/shared/render/SKILL.md +256 -0
  995. package/skills/shared/render/evals/README.md +12 -0
  996. package/skills/shared/render/evals/cases.yaml +45 -0
  997. package/skills/shared/render/references/blueprint-reference.md +203 -0
  998. package/skills/shared/render/scripts/verify.sh +167 -0
  999. package/skills/shared/replicate/SKILL.md +210 -0
  1000. package/skills/shared/replicate/evals/README.md +9 -0
  1001. package/skills/shared/replicate/evals/cases.yaml +45 -0
  1002. package/skills/shared/replicate/references/cog-packaging.md +89 -0
  1003. package/skills/shared/replicate/references/deployments-api.md +87 -0
  1004. package/skills/shared/replicate/references/webhooks-and-async.md +110 -0
  1005. package/skills/shared/replicate/scripts/verify.sh +162 -0
  1006. package/skills/shared/research-ops/SKILL.md +200 -0
  1007. package/skills/shared/research-ops/evals/README.md +13 -0
  1008. package/skills/shared/research-ops/evals/cases.yaml +38 -0
  1009. package/skills/shared/research-ops/references/credibility-rubric.md +78 -0
  1010. package/skills/shared/research-ops/references/memo-template.md +63 -0
  1011. package/skills/shared/research-ops/scripts/verify.sh +181 -0
  1012. package/skills/shared/review/SKILL.md +314 -0
  1013. package/skills/shared/review/evals/README.md +84 -0
  1014. package/skills/shared/review/evals/cases.yaml +55 -0
  1015. package/skills/shared/ruby/SKILL.md +295 -0
  1016. package/skills/shared/ruby/evals/README.md +12 -0
  1017. package/skills/shared/ruby/evals/cases.yaml +41 -0
  1018. package/skills/shared/ruby/references/gems-and-testing.md +208 -0
  1019. package/skills/shared/ruby/references/metaprogramming.md +161 -0
  1020. package/skills/shared/ruby/scripts/verify.sh +83 -0
  1021. package/skills/shared/runpod/SKILL.md +238 -0
  1022. package/skills/shared/runpod/evals/README.md +11 -0
  1023. package/skills/shared/runpod/evals/cases.yaml +47 -0
  1024. package/skills/shared/runpod/references/cost-and-scaling.md +85 -0
  1025. package/skills/shared/runpod/references/serverless-workers.md +101 -0
  1026. package/skills/shared/runpod/scripts/verify.sh +126 -0
  1027. package/skills/shared/rust/SKILL.md +320 -0
  1028. package/skills/shared/rust/evals/README.md +12 -0
  1029. package/skills/shared/rust/evals/cases.yaml +42 -0
  1030. package/skills/shared/rust/references/async-tokio.md +141 -0
  1031. package/skills/shared/rust/references/axum-service.md +132 -0
  1032. package/skills/shared/rust/references/ownership.md +86 -0
  1033. package/skills/shared/rust/references/testing.md +108 -0
  1034. package/skills/shared/rust/scripts/verify.sh +91 -0
  1035. package/skills/shared/scaling/SKILL.md +137 -0
  1036. package/skills/shared/scaling/evals/README.md +3 -0
  1037. package/skills/shared/scaling/evals/cases.yaml +42 -0
  1038. package/skills/shared/scaling/references/load-testing-k6.md +127 -0
  1039. package/skills/shared/scaling/scripts/example.load.js +24 -0
  1040. package/skills/shared/scaling/scripts/verify.sh +70 -0
  1041. package/skills/shared/sdd/SKILL.md +250 -0
  1042. package/skills/shared/sdd/evals/README.md +60 -0
  1043. package/skills/shared/sdd/evals/cases.yaml +90 -0
  1044. package/skills/shared/sdd/references/model-routing.md +194 -0
  1045. package/skills/shared/sdd-init/SKILL.md +189 -0
  1046. package/skills/shared/sdd-init/evals/README.md +3 -0
  1047. package/skills/shared/sdd-init/evals/cases.yaml +43 -0
  1048. package/skills/shared/secure-coding/SKILL.md +336 -0
  1049. package/skills/shared/secure-coding/evals/README.md +68 -0
  1050. package/skills/shared/secure-coding/evals/cases.yaml +55 -0
  1051. package/skills/shared/secure-coding/references/authn-authz.md +249 -0
  1052. package/skills/shared/secure-coding/references/owasp-by-stack.md +574 -0
  1053. package/skills/shared/secure-coding/references/secrets-and-supply-chain.md +205 -0
  1054. package/skills/shared/secure-coding/references/threat-modeling.md +213 -0
  1055. package/skills/shared/secure-coding/scripts/verify.sh +208 -0
  1056. package/skills/shared/security-scan/SKILL.md +219 -0
  1057. package/skills/shared/security-scan/evals/README.md +14 -0
  1058. package/skills/shared/security-scan/evals/cases.yaml +50 -0
  1059. package/skills/shared/security-scan/references/tools.md +97 -0
  1060. package/skills/shared/security-scan/references/triage.md +92 -0
  1061. package/skills/shared/security-scan/scripts/verify.sh +108 -0
  1062. package/skills/shared/ship/SKILL.md +272 -0
  1063. package/skills/shared/ship/evals/README.md +89 -0
  1064. package/skills/shared/ship/evals/cases.yaml +44 -0
  1065. package/skills/shared/shopify/SKILL.md +229 -0
  1066. package/skills/shared/shopify/evals/README.md +14 -0
  1067. package/skills/shared/shopify/evals/cases.yaml +41 -0
  1068. package/skills/shared/shopify/references/apps-graphql.md +103 -0
  1069. package/skills/shared/shopify/references/checkout-extensibility.md +71 -0
  1070. package/skills/shared/shopify/references/liquid-themes.md +89 -0
  1071. package/skills/shared/shopify/scripts/verify.sh +120 -0
  1072. package/skills/shared/simplify-code/SKILL.md +101 -0
  1073. package/skills/shared/simplify-code/evals/README.md +3 -0
  1074. package/skills/shared/simplify-code/evals/cases.yaml +42 -0
  1075. package/skills/shared/skill-scout/SKILL.md +169 -0
  1076. package/skills/shared/skill-scout/evals/README.md +12 -0
  1077. package/skills/shared/skill-scout/evals/cases.yaml +56 -0
  1078. package/skills/shared/skill-scout/references/install-commands.md +76 -0
  1079. package/skills/shared/skill-scout/scripts/verify.sh +154 -0
  1080. package/skills/shared/solid-js/SKILL.md +260 -0
  1081. package/skills/shared/solid-js/evals/README.md +3 -0
  1082. package/skills/shared/solid-js/evals/cases.yaml +38 -0
  1083. package/skills/shared/solid-js/references/reactivity-deep-dive.md +89 -0
  1084. package/skills/shared/solid-js/references/router-and-start.md +93 -0
  1085. package/skills/shared/solid-js/scripts/verify.sh +130 -0
  1086. package/skills/shared/source-grounded-development/SKILL.md +108 -0
  1087. package/skills/shared/source-grounded-development/evals/README.md +3 -0
  1088. package/skills/shared/source-grounded-development/evals/cases.yaml +42 -0
  1089. package/skills/shared/specify/SKILL.md +391 -0
  1090. package/skills/shared/specify/evals/README.md +73 -0
  1091. package/skills/shared/specify/evals/cases.yaml +93 -0
  1092. package/skills/shared/specify/references/eliciting-requirements.md +117 -0
  1093. package/skills/shared/specify/references/spec-template.md +101 -0
  1094. package/skills/shared/speckit-analyze/SKILL.md +35 -0
  1095. package/skills/shared/speckit-assess-decide/SKILL.md +36 -0
  1096. package/skills/shared/speckit-assess-define/SKILL.md +36 -0
  1097. package/skills/shared/speckit-assess-intake/SKILL.md +38 -0
  1098. package/skills/shared/speckit-assess-research/SKILL.md +36 -0
  1099. package/skills/shared/speckit-assess-shape/SKILL.md +36 -0
  1100. package/skills/shared/speckit-bug-assess/SKILL.md +38 -0
  1101. package/skills/shared/speckit-bug-fix/SKILL.md +37 -0
  1102. package/skills/shared/speckit-bug-test/SKILL.md +37 -0
  1103. package/skills/shared/speckit-checklist/SKILL.md +35 -0
  1104. package/skills/shared/speckit-clarify/SKILL.md +32 -0
  1105. package/skills/shared/speckit-constitution/SKILL.md +35 -0
  1106. package/skills/shared/speckit-converge/SKILL.md +36 -0
  1107. package/skills/shared/speckit-implement/SKILL.md +32 -0
  1108. package/skills/shared/speckit-plan/SKILL.md +37 -0
  1109. package/skills/shared/speckit-specify/SKILL.md +36 -0
  1110. package/skills/shared/speckit-tasks/SKILL.md +34 -0
  1111. package/skills/shared/spreadsheet-ops/SKILL.md +180 -0
  1112. package/skills/shared/spreadsheet-ops/evals/README.md +33 -0
  1113. package/skills/shared/spreadsheet-ops/evals/cases.yaml +42 -0
  1114. package/skills/shared/spreadsheet-ops/references/formula-cookbook.md +70 -0
  1115. package/skills/shared/spreadsheet-ops/references/python-excel.md +87 -0
  1116. package/skills/shared/spreadsheet-ops/references/sheets-api-appsscript.md +118 -0
  1117. package/skills/shared/spreadsheet-ops/scripts/verify.sh +152 -0
  1118. package/skills/shared/spring-boot/SKILL.md +350 -0
  1119. package/skills/shared/spring-boot/evals/README.md +11 -0
  1120. package/skills/shared/spring-boot/evals/cases.yaml +49 -0
  1121. package/skills/shared/spring-boot/references/jpa.md +94 -0
  1122. package/skills/shared/spring-boot/references/security.md +92 -0
  1123. package/skills/shared/spring-boot/references/testing.md +95 -0
  1124. package/skills/shared/spring-boot/scripts/verify.sh +115 -0
  1125. package/skills/shared/sql/SKILL.md +286 -0
  1126. package/skills/shared/sql/evals/README.md +9 -0
  1127. package/skills/shared/sql/evals/cases.yaml +49 -0
  1128. package/skills/shared/sql/references/ctes-and-recursion.md +63 -0
  1129. package/skills/shared/sql/references/joins-and-sets.md +71 -0
  1130. package/skills/shared/sql/references/portability.md +38 -0
  1131. package/skills/shared/sql/references/window-functions.md +72 -0
  1132. package/skills/shared/sql/scripts/verify.sh +139 -0
  1133. package/skills/shared/sqlite-turso/SKILL.md +214 -0
  1134. package/skills/shared/sqlite-turso/evals/README.md +24 -0
  1135. package/skills/shared/sqlite-turso/evals/cases.yaml +45 -0
  1136. package/skills/shared/sqlite-turso/references/embedded-replicas.md +96 -0
  1137. package/skills/shared/sqlite-turso/scripts/verify.sh +95 -0
  1138. package/skills/shared/structured-extraction/SKILL.md +230 -0
  1139. package/skills/shared/structured-extraction/evals/README.md +13 -0
  1140. package/skills/shared/structured-extraction/evals/cases.yaml +70 -0
  1141. package/skills/shared/structured-extraction/references/providers.md +152 -0
  1142. package/skills/shared/structured-extraction/scripts/verify.sh +160 -0
  1143. package/skills/shared/suggest/SKILL.md +123 -0
  1144. package/skills/shared/suggest/evals/README.md +17 -0
  1145. package/skills/shared/suggest/evals/cases.yaml +78 -0
  1146. package/skills/shared/supabase/SKILL.md +268 -0
  1147. package/skills/shared/supabase/evals/README.md +12 -0
  1148. package/skills/shared/supabase/evals/cases.yaml +42 -0
  1149. package/skills/shared/supabase/references/auth-ssr.md +173 -0
  1150. package/skills/shared/supabase/references/rls-cookbook.md +122 -0
  1151. package/skills/shared/supabase/scripts/verify.sh +149 -0
  1152. package/skills/shared/svelte/SKILL.md +238 -0
  1153. package/skills/shared/svelte/evals/README.md +3 -0
  1154. package/skills/shared/svelte/evals/cases.yaml +41 -0
  1155. package/skills/shared/svelte/references/runes.md +97 -0
  1156. package/skills/shared/svelte/references/sveltekit-data.md +156 -0
  1157. package/skills/shared/svelte/scripts/verify.sh +128 -0
  1158. package/skills/shared/swift-ios/SKILL.md +224 -0
  1159. package/skills/shared/swift-ios/evals/README.md +3 -0
  1160. package/skills/shared/swift-ios/evals/cases.yaml +46 -0
  1161. package/skills/shared/swift-ios/references/concurrency.md +132 -0
  1162. package/skills/shared/swift-ios/references/testing.md +112 -0
  1163. package/skills/shared/swift-ios/scripts/verify.sh +98 -0
  1164. package/skills/shared/tasks/SKILL.md +265 -0
  1165. package/skills/shared/tasks/evals/README.md +70 -0
  1166. package/skills/shared/tasks/evals/cases.yaml +75 -0
  1167. package/skills/shared/tauri/SKILL.md +219 -0
  1168. package/skills/shared/tauri/evals/README.md +12 -0
  1169. package/skills/shared/tauri/evals/cases.yaml +46 -0
  1170. package/skills/shared/tauri/references/bundling-distribution.md +129 -0
  1171. package/skills/shared/tauri/references/security.md +143 -0
  1172. package/skills/shared/tauri/scripts/verify.sh +178 -0
  1173. package/skills/shared/technical-writing/SKILL.md +230 -0
  1174. package/skills/shared/technical-writing/evals/README.md +12 -0
  1175. package/skills/shared/technical-writing/evals/cases.yaml +53 -0
  1176. package/skills/shared/technical-writing/references/diataxis-modes.md +131 -0
  1177. package/skills/shared/technical-writing/references/vale-starter.md +90 -0
  1178. package/skills/shared/technical-writing/scripts/verify.sh +83 -0
  1179. package/skills/shared/testing-go/SKILL.md +263 -0
  1180. package/skills/shared/testing-go/evals/README.md +3 -0
  1181. package/skills/shared/testing-go/evals/cases.yaml +46 -0
  1182. package/skills/shared/testing-go/references/coverage-and-benchmarks.md +85 -0
  1183. package/skills/shared/testing-go/references/mocks-and-fakes.md +140 -0
  1184. package/skills/shared/testing-go/references/synctest-and-concurrency.md +82 -0
  1185. package/skills/shared/testing-go/scripts/verify.sh +72 -0
  1186. package/skills/shared/testing-py/SKILL.md +197 -0
  1187. package/skills/shared/testing-py/evals/README.md +5 -0
  1188. package/skills/shared/testing-py/evals/cases.yaml +44 -0
  1189. package/skills/shared/testing-py/references/mocking.md +141 -0
  1190. package/skills/shared/testing-py/references/property-testing.md +99 -0
  1191. package/skills/shared/testing-py/scripts/verify.sh +117 -0
  1192. package/skills/shared/testing-web/SKILL.md +244 -0
  1193. package/skills/shared/testing-web/evals/README.md +11 -0
  1194. package/skills/shared/testing-web/evals/cases.yaml +52 -0
  1195. package/skills/shared/testing-web/references/jest-setup.md +88 -0
  1196. package/skills/shared/testing-web/references/recipes.md +116 -0
  1197. package/skills/shared/testing-web/scripts/verify.sh +111 -0
  1198. package/skills/shared/together-fireworks/SKILL.md +176 -0
  1199. package/skills/shared/together-fireworks/evals/README.md +3 -0
  1200. package/skills/shared/together-fireworks/evals/cases.yaml +50 -0
  1201. package/skills/shared/together-fireworks/references/batch-and-tuning.md +59 -0
  1202. package/skills/shared/together-fireworks/references/models-and-pricing.md +79 -0
  1203. package/skills/shared/together-fireworks/scripts/verify.sh +165 -0
  1204. package/skills/shared/training-data/SKILL.md +226 -0
  1205. package/skills/shared/training-data/evals/README.md +13 -0
  1206. package/skills/shared/training-data/evals/cases.yaml +41 -0
  1207. package/skills/shared/training-data/references/formats.md +118 -0
  1208. package/skills/shared/training-data/references/synthesis-dedup-quality.md +134 -0
  1209. package/skills/shared/translation-l10n/SKILL.md +229 -0
  1210. package/skills/shared/translation-l10n/evals/README.md +3 -0
  1211. package/skills/shared/translation-l10n/evals/cases.yaml +39 -0
  1212. package/skills/shared/translation-l10n/references/icu-cookbook.md +82 -0
  1213. package/skills/shared/translation-l10n/references/rtl-and-bidi.md +60 -0
  1214. package/skills/shared/typescript/SKILL.md +225 -0
  1215. package/skills/shared/typescript/evals/README.md +15 -0
  1216. package/skills/shared/typescript/evals/cases.yaml +46 -0
  1217. package/skills/shared/typescript/references/build-and-monorepo.md +141 -0
  1218. package/skills/shared/typescript/references/type-system.md +162 -0
  1219. package/skills/shared/typescript/scripts/verify.sh +52 -0
  1220. package/skills/shared/ui-engineering/SKILL.md +113 -0
  1221. package/skills/shared/ui-engineering/evals/cases.yaml +38 -0
  1222. package/skills/shared/unity/SKILL.md +282 -0
  1223. package/skills/shared/unity/evals/README.md +8 -0
  1224. package/skills/shared/unity/evals/cases.yaml +43 -0
  1225. package/skills/shared/unity/references/build-and-platforms.md +65 -0
  1226. package/skills/shared/unity/references/input-system.md +102 -0
  1227. package/skills/shared/unity/references/lifecycle-deep-dive.md +75 -0
  1228. package/skills/shared/unreal/SKILL.md +270 -0
  1229. package/skills/shared/unreal/evals/README.md +12 -0
  1230. package/skills/shared/unreal/evals/cases.yaml +45 -0
  1231. package/skills/shared/unreal/references/blueprint-cpp-parity.md +109 -0
  1232. package/skills/shared/unreal/references/gameplay-framework.md +95 -0
  1233. package/skills/shared/unreal/references/packaging.md +83 -0
  1234. package/skills/shared/unslop/SKILL.md +143 -0
  1235. package/skills/shared/unslop/evals/README.md +25 -0
  1236. package/skills/shared/unslop/evals/cases.yaml +61 -0
  1237. package/skills/shared/unsloth/SKILL.md +254 -0
  1238. package/skills/shared/unsloth/evals/README.md +10 -0
  1239. package/skills/shared/unsloth/evals/cases.yaml +42 -0
  1240. package/skills/shared/unsloth/references/export.md +75 -0
  1241. package/skills/shared/unsloth/references/grpo.md +91 -0
  1242. package/skills/shared/unsloth/references/masking-and-templates.md +91 -0
  1243. package/skills/shared/variant-explorer/SKILL.md +93 -0
  1244. package/skills/shared/variant-explorer/evals/cases.yaml +38 -0
  1245. package/skills/shared/vector-db/SKILL.md +189 -0
  1246. package/skills/shared/vector-db/evals/README.md +10 -0
  1247. package/skills/shared/vector-db/evals/cases.yaml +45 -0
  1248. package/skills/shared/vector-db/references/engines.md +175 -0
  1249. package/skills/shared/vector-db/references/tuning.md +62 -0
  1250. package/skills/shared/vector-db/scripts/verify.sh +110 -0
  1251. package/skills/shared/vercel/SKILL.md +242 -0
  1252. package/skills/shared/vercel/evals/README.md +23 -0
  1253. package/skills/shared/vercel/evals/cases.yaml +45 -0
  1254. package/skills/shared/vercel/references/cli-cookbook.md +98 -0
  1255. package/skills/shared/vercel/references/vercel-json.md +120 -0
  1256. package/skills/shared/vercel/scripts/verify.sh +168 -0
  1257. package/skills/shared/verify/SKILL.md +230 -0
  1258. package/skills/shared/verify/evals/README.md +78 -0
  1259. package/skills/shared/verify/evals/cases.yaml +104 -0
  1260. package/skills/shared/vllm/SKILL.md +235 -0
  1261. package/skills/shared/vllm/evals/README.md +9 -0
  1262. package/skills/shared/vllm/evals/cases.yaml +47 -0
  1263. package/skills/shared/vllm/references/flags-and-endpoints.md +105 -0
  1264. package/skills/shared/vllm/references/memory-and-throughput.md +78 -0
  1265. package/skills/shared/vue-nuxt/SKILL.md +323 -0
  1266. package/skills/shared/vue-nuxt/evals/README.md +11 -0
  1267. package/skills/shared/vue-nuxt/evals/cases.yaml +49 -0
  1268. package/skills/shared/vue-nuxt/references/data-and-state.md +127 -0
  1269. package/skills/shared/vue-nuxt/references/migration-nuxt4.md +79 -0
  1270. package/skills/shared/vue-nuxt/references/nitro-and-rendering.md +117 -0
  1271. package/skills/shared/vue-nuxt/references/reactivity.md +135 -0
  1272. package/skills/shared/vue-nuxt/scripts/verify.sh +148 -0
  1273. package/skills/shared/webhooks/SKILL.md +229 -0
  1274. package/skills/shared/webhooks/evals/README.md +15 -0
  1275. package/skills/shared/webhooks/evals/cases.yaml +46 -0
  1276. package/skills/shared/webhooks/references/framework-raw-body.md +97 -0
  1277. package/skills/shared/webhooks/references/signature-schemes.md +66 -0
  1278. package/skills/shared/webhooks/scripts/verify.sh +142 -0
  1279. package/skills/shared/wordpress/SKILL.md +224 -0
  1280. package/skills/shared/wordpress/evals/README.md +3 -0
  1281. package/skills/shared/wordpress/evals/cases.yaml +50 -0
  1282. package/skills/shared/wordpress/references/hardening.md +108 -0
  1283. package/skills/shared/wordpress/references/performance.md +80 -0
  1284. package/skills/shared/wordpress/references/woocommerce.md +65 -0
  1285. package/skills/shared/wordpress/scripts/verify.sh +96 -0
  1286. package/skills/shared/worktrees/SKILL.md +204 -0
  1287. package/skills/shared/worktrees/evals/README.md +78 -0
  1288. package/skills/shared/worktrees/evals/cases.yaml +47 -0
@@ -0,0 +1,909 @@
1
+ // probe() runs INSIDE the page (it is serialised and evaluated through cdp.mjs), so it must stay
2
+ // self-contained: no imports, no closures over module scope.
3
+ // It measures what a design reviewer looks at — headline line counts, the type system, colour,
4
+ // surfaces, decoration — and the specific tells this skill bans.
5
+ export function probe() {
6
+ const W = innerWidth, H = innerHeight, SY = scrollY;
7
+ const round = (n, d = 2) => Math.round(n * 10 ** d) / 10 ** d;
8
+
9
+ // --- colour normalisation (handles oklch(), color(), named colours, etc.)
10
+ const cvs = document.createElement('canvas'); cvs.width = cvs.height = 1;
11
+ const ctx = cvs.getContext('2d', { willReadFrequently: true });
12
+ const colorCache = new Map();
13
+ const rgba = (c) => {
14
+ if (colorCache.has(c)) return colorCache.get(c);
15
+ ctx.clearRect(0, 0, 1, 1); ctx.fillStyle = 'rgba(0,0,0,0)'; ctx.fillStyle = c; ctx.fillRect(0, 0, 1, 1);
16
+ const d = ctx.getImageData(0, 0, 1, 1).data;
17
+ const v = { r: d[0], g: d[1], b: d[2], a: round(d[3] / 255) };
18
+ colorCache.set(c, v); return v;
19
+ };
20
+ const hex = (v) => '#' + [v.r, v.g, v.b].map((n) => n.toString(16).padStart(2, '0')).join('');
21
+ const lum = (v) => {
22
+ const f = (x) => { x /= 255; return x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4; };
23
+ return 0.2126 * f(v.r) + 0.7152 * f(v.g) + 0.0722 * f(v.b);
24
+ };
25
+ const contrast = (a, b) => { const l1 = lum(a), l2 = lum(b); return round((Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05), 1); };
26
+ const hsl = (v) => {
27
+ const r = v.r / 255, g = v.g / 255, b = v.b / 255, mx = Math.max(r, g, b), mn = Math.min(r, g, b);
28
+ const l = (mx + mn) / 2; let h = 0, s = 0;
29
+ if (mx !== mn) {
30
+ const d = mx - mn; s = l > 0.5 ? d / (2 - mx - mn) : d / (mx + mn);
31
+ h = mx === r ? (g - b) / d + (g < b ? 6 : 0) : mx === g ? (b - r) / d + 2 : (r - g) / d + 4; h *= 60;
32
+ }
33
+ return { h: Math.round(h), s: round(s), l: round(l) };
34
+ };
35
+
36
+ // --- monospace detection by measurement, not by font name
37
+ const mctx = document.createElement('canvas').getContext('2d');
38
+ const monoCache = new Map();
39
+ const isMono = (family) => {
40
+ if (monoCache.has(family)) return monoCache.get(family);
41
+ mctx.font = `40px ${family}`;
42
+ const a = mctx.measureText('iiiiiiii').width, b = mctx.measureText('WWWWWWWW').width;
43
+ const v = Math.abs(a - b) < 1;
44
+ monoCache.set(family, v); return v;
45
+ };
46
+ const firstFam = (ff) => ff.split(',')[0].replace(/["']/g, '').trim();
47
+ // Text kept for screen readers only (the usual .sr-only / .visually-hidden patterns) is not on the page.
48
+ const srHidden = (el) => {
49
+ for (let p = el, i = 0; p && p !== document.body && i < 4; p = p.parentElement, i++) {
50
+ const cs = getComputedStyle(p); const r = p.getBoundingClientRect();
51
+ if (cs.clip === 'rect(0px, 0px, 0px, 0px)' || /inset\(50%\)/.test(cs.clipPath) || (r.width <= 2 && r.height <= 2 && cs.overflow !== 'visible')) return true;
52
+ }
53
+ return false;
54
+ };
55
+ const GENERIC = /^(serif|sans-serif|monospace|system-ui|ui-sans-serif|ui-serif|ui-monospace|ui-rounded|cursive|fantasy|inherit|-apple-system|BlinkMacSystemFont|Segoe UI|Arial|Helvetica|Helvetica Neue|Times New Roman|Times|Georgia|Verdana|Tahoma|Courier New|Courier|Roboto|Apple Color Emoji)$/i;
56
+
57
+ // --- collect elements
58
+ const all = [...document.body.querySelectorAll('*')].slice(0, 9000);
59
+ const texts = [];
60
+ const big = [];
61
+ for (const el of all) {
62
+ const tag = el.tagName;
63
+ if (tag === 'SCRIPT' || tag === 'STYLE' || tag === 'NOSCRIPT' || tag === 'TEMPLATE') continue;
64
+ const r = el.getBoundingClientRect();
65
+ if (r.width < 1 || r.height < 1) continue;
66
+ const cs = getComputedStyle(el);
67
+ if (cs.display === 'none' || cs.visibility === 'hidden') continue;
68
+ const area = r.width * r.height;
69
+ if (area >= 60 * 30) big.push({ el, r, cs, area, top: r.top + SY });
70
+ let direct = '';
71
+ for (const n of el.childNodes) if (n.nodeType === 3) direct += n.nodeValue;
72
+ direct = direct.replace(/\s+/g, ' ').trim();
73
+ if (!direct) continue;
74
+ if (tag === 'OPTION' || el.closest('svg') || srHidden(el)) continue;
75
+ const size = parseFloat(cs.fontSize);
76
+ if (!size || size < 6) continue;
77
+ const ls = cs.letterSpacing === 'normal' ? 0 : parseFloat(cs.letterSpacing) / size;
78
+ const lh = cs.lineHeight === 'normal' ? 1.2 : parseFloat(cs.lineHeight) / size;
79
+ texts.push({
80
+ el, tag, text: direct, chars: direct.length, fam: firstFam(cs.fontFamily), ff: cs.fontFamily,
81
+ size: round(size, 1), weight: +cs.fontWeight || 400, ls: round(ls, 3), lh: round(lh, 2),
82
+ upper: cs.textTransform === 'uppercase' || (direct.length > 2 && direct === direct.toUpperCase() && /[A-Z]{2}/.test(direct)),
83
+ italic: cs.fontStyle === 'italic', align: cs.textAlign, color: cs.color,
84
+ top: r.top + SY, bottom: r.bottom + SY, left: r.left, right: r.right, w: r.width, h: r.height,
85
+ clipText: (cs.webkitBackgroundClip || cs.backgroundClip || '').includes('text'),
86
+ inCode: !!el.closest('pre, code, kbd, samp, textarea'), inUI: !!el.closest('[data-ui]'), textShadow: cs.textShadow !== 'none',
87
+ });
88
+ }
89
+
90
+ // --- heading blocks: climb from a large text node to the block that holds the whole headline
91
+ // An element that is a complete text unit by itself: separate links, list items or paragraphs set large
92
+ // are separate lines of content, never the wrapped lines of one headline.
93
+ const UNIT = 'h1, h2, h3, h4, h5, h6, [role="heading"], p, li, a, button, blockquote, dt, dd, figcaption, td, th, label, summary';
94
+ const blockOf = (t) => {
95
+ let cur = t.el;
96
+ // usable as the block when it holds only this size of type (a title with its own small description is not one block)
97
+ const clean = (el) => (el.innerText || '').trim().length <= 320 && !texts.some((o) => o.el !== t.el && el.contains(o.el) && o.size < t.size * 0.72);
98
+ let unit = t.el.closest(UNIT);
99
+ if (unit && unit.matches('a, button')) { const outer = unit.parentElement?.closest('h1, h2, h3, h4, h5, h6, p, blockquote, li'); if (outer && clean(outer)) unit = outer; }
100
+ if (unit && clean(unit)) return unit;
101
+ while (cur.parentElement && cur.parentElement !== document.body) {
102
+ if (cur.matches(UNIT)) break;
103
+ const p = cur.parentElement;
104
+ if (p.matches('ul, ol, dl, nav, menu, table, tbody')) break;
105
+ const inner = (p.innerText || '').trim();
106
+ if (inner.length > 220) break;
107
+ let ok = true;
108
+ for (const o of texts) if (o.el !== t.el && p.contains(o.el) && o.size < t.size * 0.72) { ok = false; break; }
109
+ if (!ok) break;
110
+ cur = p;
111
+ }
112
+ return cur;
113
+ };
114
+ // Count lines from the text itself: inline images or media set inside a sentence are not lines.
115
+ const linesOf = (el, size) => {
116
+ const tops = [];
117
+ const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
118
+ const range = document.createRange();
119
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {
120
+ if (!node.nodeValue.trim()) continue;
121
+ range.selectNodeContents(node);
122
+ for (const rc of range.getClientRects()) {
123
+ if (rc.width < 1 || rc.height < size * 0.35) continue;
124
+ const mid = rc.top + rc.height / 2;
125
+ if (!tops.some((t) => Math.abs(t - mid) < size * 0.55)) tops.push(mid);
126
+ }
127
+ }
128
+ return Math.max(1, tops.length);
129
+ };
130
+ const describeBlock = (el, t) => {
131
+ const r = el.getBoundingClientRect();
132
+ const text = (el.innerText || t.text).replace(/\s+/g, ' ').trim();
133
+ const cx = (r.left + r.right) / 2 / W;
134
+ const fams = new Set(texts.filter((o) => el.contains(o.el)).map((o) => o.fam + (o.italic ? ' italic' : '')));
135
+ return {
136
+ text: text.slice(0, 200), words: text.split(' ').filter(Boolean).length, chars: text.length,
137
+ lines: linesOf(el, t.size), size: t.size, vw: round((t.size / W) * 100, 1), font: t.fam, weight: t.weight,
138
+ tracking: t.ls, leading: t.lh, upper: t.upper, italic: t.italic, mono: isMono(t.ff),
139
+ align: /center/.test(t.align) ? 'center' : /right|end/.test(t.align) ? 'right' : 'left',
140
+ position: cx < 0.42 ? 'left' : cx > 0.58 ? 'right' : 'center',
141
+ widthRatio: round(r.width / W), top: Math.round(r.top + SY), mixedFonts: fams.size > 1 ? [...fams] : undefined,
142
+ gradientText: texts.some((o) => el.contains(o.el) && o.clipText) || undefined, tag: el.tagName.toLowerCase(),
143
+ // a title (h1–h6, or unmarked large text) or a statement (a paragraph, quotation or list item set large)
144
+ kind: el.closest('h1,h2,h3,h4,h5,h6,[role="heading"]') || el.querySelector('h1,h2,h3,h4,h5,h6,[role="heading"]') ? 'heading'
145
+ : t.el.closest('p, blockquote, li, dd, q, figcaption') || text.length > 150 ? 'statement' : 'heading',
146
+ };
147
+ };
148
+ const headingMin = W < 600 ? 22 : Math.max(26, W * 0.019);
149
+ const seenBlocks = new Set();
150
+ const headings = [];
151
+ for (const t of texts.filter((x) => x.size >= headingMin && !x.inCode).sort((a, b) => b.size - a.size)) {
152
+ const blk = blockOf(t);
153
+ if (seenBlocks.has(blk) || [...seenBlocks].some((b) => b.contains(blk) || blk.contains(b))) continue;
154
+ seenBlocks.add(blk);
155
+ const d = describeBlock(blk, t);
156
+ if (d.kind === 'statement' && d.chars > 150 && d.size <= 30) continue; // a lead paragraph, not display type
157
+ headings.push({ ...d, _el: blk });
158
+ }
159
+ headings.sort((a, b) => a.top - b.top);
160
+
161
+ // hero headline = largest heading block that starts inside the first viewport
162
+ const inHero = headings.filter((h) => h.top < H * 0.95);
163
+ let hero = inHero.slice().sort((a, b) => b.size - a.size || b.chars - a.chars)[0] || null;
164
+ if (hero) hero.kind = 'heading'; // the main line of the first screen is a headline whatever its tag
165
+ const h1el = document.querySelector('h1');
166
+ if (!hero) {
167
+ // quiet pages: fall back to the h1, then to the largest text in the first viewport
168
+ const h1r = h1el && h1el.getBoundingClientRect();
169
+ const pick = (h1r && h1r.top < H * 0.95 && h1r.width > 0 && texts.find((t) => h1el.contains(t.el))) ||
170
+ texts.filter((t) => t.top - SY < H * 0.95 && t.size >= 18 && t.chars >= 8).sort((a, b) => b.size - a.size)[0];
171
+ if (pick) { const blk = h1el && h1el.contains(pick.el) ? h1el : blockOf(pick); hero = { ...describeBlock(blk, pick), _el: blk }; }
172
+ }
173
+ let h1 = null;
174
+ if (h1el && h1el.getBoundingClientRect().width > 0) {
175
+ const cs = getComputedStyle(h1el); const size = parseFloat(cs.fontSize);
176
+ const text = (h1el.innerText || '').replace(/\s+/g, ' ').trim();
177
+ h1 = { text: text.slice(0, 200), words: text.split(' ').filter(Boolean).length, lines: linesOf(h1el, size), size: round(size, 1), font: firstFam(cs.fontFamily) };
178
+ }
179
+
180
+ // --- eyebrows: a small short label sitting directly above a heading block
181
+ // Sticky and fixed layers overlap other content by design; their geometry says nothing about what labels what.
182
+ const pinned = (el) => { for (let p = el; p && p !== document.body; p = p.parentElement) { const pos = getComputedStyle(p).position; if (pos === 'fixed' || pos === 'sticky') return true; } return false; };
183
+ const eyebrows = [];
184
+ for (const h of headings) {
185
+ const hr = h._el.getBoundingClientRect(); const htop = hr.top + SY;
186
+ if (h._el.closest('[data-ui]')) continue;
187
+ const cand = texts.filter((t) => !t.inUI && !h._el.contains(t.el) && t.size <= Math.min(18, h.size * 0.5) && t.chars <= 44 && t.chars >= 2 &&
188
+ t.bottom <= htop + 4 && htop - t.bottom <= Math.min(140, Math.max(72, h.size * 1.1)) && t.right > hr.left - 8 && t.left < hr.right + 8 &&
189
+ !t.el.closest('button') &&
190
+ !t.el.closest('nav, [role="navigation"], footer') && !(t.top < 120 && t.el.closest('header')) && !pinned(t.el) && pinned(h._el) === false)
191
+ .sort((a, b) => b.bottom - a.bottom)[0];
192
+ if (!cand) continue;
193
+ // must be alone on its row (a real paragraph above a heading is not an eyebrow)
194
+ if (texts.some((t) => t !== cand && Math.abs(t.top - cand.top) < 6 && t.chars > 44)) continue;
195
+ // a note that sits closer to the heading above it than to this one belongs to that heading
196
+ const gapDown = htop - cand.bottom;
197
+ if (headings.some((o) => o !== h && !o._el.contains(cand.el) && (() => { const or = o._el.getBoundingClientRect(); const gapUp = cand.top - (or.bottom + SY); return gapUp >= -2 && gapUp < gapDown && or.left < cand.right && or.right > cand.left; })())) continue;
198
+ let pill = false; let p = cand.el;
199
+ for (let i = 0; i < 3 && p; i++, p = p.parentElement) {
200
+ const cs = getComputedStyle(p); const pr = p.getBoundingClientRect();
201
+ if (pr.width < 420 && pr.height < 56 && parseFloat(cs.borderTopLeftRadius) >= 10 && (rgba(cs.backgroundColor).a > 0.04 || parseFloat(cs.borderTopWidth) > 0)) pill = true;
202
+ }
203
+ eyebrows.push({ text: cand.text.slice(0, 50), above: h.text.slice(0, 40), upper: cand.upper, tracked: cand.ls >= 0.04, mono: isMono(cand.ff), pill, size: cand.size, top: Math.round(cand.top) });
204
+ }
205
+
206
+ // --- decorative numbering: 01 / 02 / (03) / 04. / 001 / "Step 02"
207
+ const numbered = texts.filter((t) => !t.inCode && !t.inUI && (/^[(\[\/#]?\s?0\d{1,2}\s?[)\]\/.:—–-]?$/.test(t.text) || /^0\d\s?[\/.—–-]\s/.test(t.text) || /^(step|phase|no\.?|n°|nº|chapter|part|fig\.?)\s?0\d{1,2}\b/i.test(t.text)));
208
+
209
+ // --- type system
210
+ const famChars = new Map(); const sizeChars = new Map(); let totalChars = 0; let monoChars = 0; let monoProse = 0; const monoFams = new Set(); const monoSamples = [];
211
+ let microLabels = 0; let microLabelChars = 0; let upperChars = 0; let centerChars = 0; const microSamples = [];
212
+ for (const t of texts) {
213
+ totalChars += t.chars;
214
+ famChars.set(t.fam, (famChars.get(t.fam) || 0) + t.chars);
215
+ if (!t.inUI) { const s = Math.round(t.size); sizeChars.set(s, (sizeChars.get(s) || 0) + t.chars); } // interface fragments keep their own sizes
216
+ if (isMono(t.ff)) {
217
+ monoChars += t.chars; monoFams.add(t.fam);
218
+ if (!t.inCode) { monoProse += t.chars; if (monoSamples.length < 6) monoSamples.push(t.text.slice(0, 40)); }
219
+ }
220
+ if (t.size <= 14 && t.upper && t.ls >= 0.05 && !t.inUI && !t.el.closest('nav, button, a[href]')) { microLabels++; microLabelChars += t.chars; if (microSamples.length < 6) microSamples.push(t.text.slice(0, 40)); }
221
+ if (t.upper) upperChars += t.chars;
222
+ if (/center/.test(t.align)) centerChars += t.chars;
223
+ }
224
+ const families = [...famChars.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6).map(([family, chars]) => ({ family, share: round(chars / totalChars), mono: isMono(`"${family}", serif`) && isMono(`"${family}", sans-serif`) }));
225
+ const bodyTexts = texts.filter((t) => t.size >= 10 && t.size <= 24 && t.chars >= 60 && !t.inCode && !t.inUI);
226
+ const bodyBy = new Map();
227
+ for (const t of bodyTexts) { const k = t.fam + '|' + Math.round(t.size); bodyBy.set(k, (bodyBy.get(k) || 0) + t.chars); }
228
+ const bodyTop = [...bodyBy.entries()].sort((a, b) => b[1] - a[1])[0];
229
+ const bodySample = bodyTop ? bodyTexts.find((t) => t.fam + '|' + Math.round(t.size) === bodyTop[0]) : null;
230
+ const longest = bodyTexts.slice().sort((a, b) => b.chars - a.chars)[0];
231
+ const sizes = [...sizeChars.entries()].sort((a, b) => a[0] - b[0]).map(([s]) => s);
232
+ const maxText = texts.slice().sort((a, b) => b.size - a.size)[0];
233
+ const smallText = texts.filter((t) => t.size < 13 && t.chars >= 12 && !t.inUI && !t.inCode).length; // interface fragments keep their true sizes
234
+ const loaded = new Set([...document.fonts].filter((f) => f.status === 'loaded').map((f) => f.family.replace(/["']/g, '').toLowerCase()));
235
+ const declared = new Set([...document.fonts].map((f) => f.family.replace(/["']/g, '').toLowerCase()));
236
+ const notLoaded = families.map((f) => f.family).filter((f) => !GENERIC.test(f) && !loaded.has(f.toLowerCase()) && (declared.has(f.toLowerCase()) || declared.size > 0));
237
+
238
+ // --- colours
239
+ const bodyBg = (() => {
240
+ for (const el of [document.body, document.documentElement]) { const v = rgba(getComputedStyle(el).backgroundColor); if (v.a > 0.5) return v; }
241
+ return { r: 255, g: 255, b: 255, a: 1 };
242
+ })();
243
+ const sectionBgs = [];
244
+ for (const b of big) {
245
+ if (b.area < W * H * 0.3) continue;
246
+ const v = rgba(b.cs.backgroundColor); if (v.a < 0.9) continue;
247
+ const hx = hex(v); if (!sectionBgs.some((s) => s.color === hx)) sectionBgs.push({ color: hx, top: Math.round(b.top), hsl: hsl(v) });
248
+ }
249
+ const textColorChars = new Map();
250
+ for (const t of texts) { const v = rgba(t.color); if (v.a < 0.3) continue; const hx = hex(v); textColorChars.set(hx, (textColorChars.get(hx) || 0) + t.chars); }
251
+ const textColors = [...textColorChars.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([color, c]) => ({ color, share: round(c / totalChars) }));
252
+
253
+ // effective background behind an element (null when an image or gradient sits behind it)
254
+ const bgBehind = (el) => {
255
+ for (let p = el; p && p !== document.documentElement; p = p.parentElement) {
256
+ const cs = getComputedStyle(p);
257
+ if (cs.backgroundImage !== 'none' || p.tagName === 'VIDEO' || p.tagName === 'CANVAS') return null;
258
+ const v = rgba(cs.backgroundColor); if (v.a >= 0.9) return v;
259
+ if (cs.position === 'absolute' || cs.position === 'fixed') return null; // may overlap media
260
+ }
261
+ return bodyBg;
262
+ };
263
+ // media that may sit behind text without being its ancestor (an absolutely placed hero image, a video layer)
264
+ const mediaRects = big.filter((b) => b.area >= 120 * 80 && (b.el.tagName === 'IMG' || b.el.tagName === 'VIDEO' || b.el.tagName === 'CANVAS' || b.el.tagName === 'PICTURE' || b.el.tagName === 'IFRAME' || b.el.tagName === 'svg' || b.cs.backgroundImage !== 'none')).map((b) => ({ el: b.el, l: b.r.left, r: b.r.right, t: b.r.top + SY, b: b.r.bottom + SY }));
265
+ const overMedia = (t) => {
266
+ const area = Math.max(1, t.w * t.h);
267
+ for (const m of mediaRects) {
268
+ const ox = Math.min(t.right, m.r) - Math.max(t.left, m.l), oy = Math.min(t.bottom, m.b) - Math.max(t.top, m.t);
269
+ if (ox > 0 && oy > 0 && (ox * oy) / area >= 0.25) return true;
270
+ }
271
+ return false;
272
+ };
273
+ const lowContrast = [];
274
+ for (const t of texts) {
275
+ if (t.chars < 4 || t.top - SY > H * 6) continue;
276
+ const fg = rgba(t.color); if (fg.a < 0.99) continue;
277
+ if (overMedia(t)) continue; // cannot know the pixels behind it; judge that from the screenshot
278
+ // text blended with difference or exclusion inverts whatever lies under it; its colour alone says nothing
279
+ let blended = false;
280
+ for (let p = t.el, i = 0; p && p !== document.body && i < 6; p = p.parentElement, i++) if (/difference|exclusion/.test(getComputedStyle(p).mixBlendMode)) { blended = true; break; }
281
+ if (blended) continue;
282
+ const bg = bgBehind(t.el); if (!bg) continue;
283
+ const c = contrast(fg, bg); const need = t.size >= 24 || (t.size >= 18.5 && t.weight >= 700) ? 3 : 4.5;
284
+ if (c < need) lowContrast.push({ text: t.text.slice(0, 40), ratio: c, need, size: t.size, fg: hex(fg), bg: hex(bg) });
285
+ }
286
+ lowContrast.sort((a, b) => a.ratio - b.ratio);
287
+
288
+ // --- buttons
289
+ const buttons = [];
290
+ for (const el of document.querySelectorAll('a, button, [role="button"], input[type="submit"]')) {
291
+ const r = el.getBoundingClientRect(); if (r.height < 28 || r.height > 84 || r.width < 64 || r.width > Math.min(W * 0.96, 900)) continue;
292
+ const cs = getComputedStyle(el); let bg = rgba(cs.backgroundColor); const bw = parseFloat(cs.borderTopWidth) || 0;
293
+ const grad = cs.backgroundImage.includes('gradient(');
294
+ if (grad && bg.a < 0.5) { const first = cs.backgroundImage.match(/(rgba?\([^)]+\)|oklch\([^)]+\)|color\([^)]+\)|#[0-9a-f]{3,8})/i); if (first) bg = rgba(first[1]); }
295
+ if (bg.a < 0.5 && bw < 1) continue;
296
+ const rad = parseFloat(cs.borderTopLeftRadius) || 0;
297
+ buttons.push({
298
+ text: (el.innerText || el.value || '').replace(/\s+/g, ' ').trim().slice(0, 40), filled: bg.a >= 0.5, bg: bg.a >= 0.5 ? hex(bg) : null, bgHsl: bg.a >= 0.5 ? hsl(bg) : null, fg: hex(rgba(cs.color)),
299
+ radius: rad >= r.height / 2 - 1 ? 'pill' : Math.round(rad), h: Math.round(r.height), top: Math.round(r.top + SY),
300
+ shadow: cs.boxShadow !== 'none' && !/^rgba?\(0, 0, 0, 0\)/.test(cs.boxShadow), gradient: grad, upper: cs.textTransform === 'uppercase', mono: isMono(cs.fontFamily),
301
+ });
302
+ }
303
+ const mode = (arr) => { const m = new Map(); for (const a of arr) m.set(JSON.stringify(a), (m.get(JSON.stringify(a)) || 0) + 1); const top = [...m.entries()].sort((a, b) => b[1] - a[1])[0]; return top ? JSON.parse(top[0]) : undefined; };
304
+ const filledBtns = buttons.filter((b) => b.filled);
305
+
306
+ // --- surfaces and decoration
307
+ const isOverlay = (el) => { for (let p = el, i = 0; p && i < 4; p = p.parentElement, i++) { const pos = getComputedStyle(p).position; if (pos === 'fixed' || pos === 'sticky') return true; if (p.matches('dialog, [role="dialog"], [role="menu"], [role="tooltip"], [popover]')) return true; } return false; };
308
+ // real tables, form fields and interface fragments (marked data-ui) keep their own lines
309
+ const formish = (el) => el.matches('input, textarea, select, table, thead, tbody, tr, td, th, hr') || !!el.closest('table, [data-ui]');
310
+ let shadowStatic = 0, shadowOverlay = 0; const shadowSamples = []; let gradients = 0; let scrims = 0; const gradientSamples = []; let gradientText = 0;
311
+ let hairlines = 0; const hairlineSamples = []; let outlinedBoxes = 0; let blurred = 0; let roundedBoxes = 0; let squareMedia = 0; let roundedMedia = 0; const mediaRadii = []; const boxRadii = [];
312
+ let dashed = 0;
313
+ const label = (el) => (el.tagName.toLowerCase() + (el.id ? '#' + el.id : '') + (typeof el.className === 'string' && el.className ? '.' + el.className.trim().split(/\s+/).slice(0, 2).join('.') : '')).slice(0, 60);
314
+ for (const b of big) {
315
+ const { cs, r, el } = b;
316
+ const sh = cs.boxShadow;
317
+ if (sh !== 'none' && !/^rgba?\(0, 0, 0, 0\) 0px 0px 0px 0px$/.test(sh)) {
318
+ const outer = sh.split(/,(?![^(]*\))/).some((part) => !/inset/.test(part) && !/ 0px 0px 0px 0px/.test(part) && !/rgba\(\d+, \d+, \d+, 0\)/.test(part));
319
+ if (outer) { if (isOverlay(el) || el.closest('[data-ui]')) shadowOverlay++; else { shadowStatic++; if (shadowSamples.length < 4) shadowSamples.push(label(el) + ' → ' + sh.slice(0, 80)); } }
320
+ }
321
+ if (cs.backgroundImage.includes('gradient(')) {
322
+ if ((cs.webkitBackgroundClip || cs.backgroundClip || '').includes('text')) gradientText++;
323
+ else if (b.area >= W * H * 0.02) {
324
+ // neutral stops with changing alpha = a legibility scrim over media, not decoration
325
+ const stops = [...cs.backgroundImage.matchAll(/rgba?\(([^)]+)\)/g)].map((mm) => mm[1].split(/[,\s/]+/).filter(Boolean).map(Number));
326
+ const exotic = /oklch|oklab|color\(|lab\(|lch\(|hsl/.test(cs.backgroundImage);
327
+ const scrim = !exotic && stops.length > 0 && stops.every((c) => Math.max(c[0], c[1], c[2]) - Math.min(c[0], c[1], c[2]) <= 14) && stops.some((c) => c.length > 3 && c[3] < 1);
328
+ if (scrim) scrims++; else { gradients++; if (gradientSamples.length < 4) gradientSamples.push(label(el) + ' → ' + cs.backgroundImage.slice(0, 110)); }
329
+ }
330
+ }
331
+ if ((cs.backdropFilter && cs.backdropFilter !== 'none') || (cs.webkitBackdropFilter && cs.webkitBackdropFilter !== 'none')) blurred++;
332
+ const bt = parseFloat(cs.borderTopWidth) || 0, bb = parseFloat(cs.borderBottomWidth) || 0, bl = parseFloat(cs.borderLeftWidth) || 0, br = parseFloat(cs.borderRightWidth) || 0;
333
+ const thin = (w, c, style) => w > 0 && w <= 1.5 && style !== 'none' && rgba(c).a > 0.02;
334
+ const topLine = thin(bt, cs.borderTopColor, cs.borderTopStyle), botLine = thin(bb, cs.borderBottomColor, cs.borderBottomStyle);
335
+ const leftLine = thin(bl, cs.borderLeftColor, cs.borderLeftStyle), rightLine = thin(br, cs.borderRightColor, cs.borderRightStyle);
336
+ if (!formish(el)) {
337
+ if (topLine && botLine && leftLine && rightLine) { if (r.width >= 150 && r.height >= 80) outlinedBoxes++; }
338
+ else if ((r.width >= 240 && (topLine || botLine)) || (r.height >= 240 && (leftLine || rightLine))) { hairlines++; if (hairlineSamples.length < 5) hairlineSamples.push(label(el) + ' ' + Math.round(r.width) + 'px'); }
339
+ // thin dashed or dotted lines; a heavy dotted edge (3px or more) is a drawn perforation, not a hairline
340
+ if ([[cs.borderTopStyle, bt], [cs.borderBottomStyle, bb], [cs.borderLeftStyle, bl], [cs.borderRightStyle, br]].some(([st, w]) => /dashed|dotted/.test(st) && w > 0 && w < 3)) dashed++;
341
+ }
342
+ const rad = parseFloat(cs.borderTopLeftRadius) || 0;
343
+ const isMedia = el.tagName === 'IMG' || el.tagName === 'VIDEO' || el.tagName === 'CANVAS' || /url\(/.test(cs.backgroundImage);
344
+ if (isMedia && r.width >= 200 && r.height >= 140) {
345
+ let effective = rad; let p = el.parentElement;
346
+ for (let i = 0; i < 3 && p && effective === 0; i++, p = p.parentElement) { const pcs = getComputedStyle(p); if (pcs.overflow !== 'visible') effective = parseFloat(pcs.borderTopLeftRadius) || 0; }
347
+ if (effective > 1) { roundedMedia++; mediaRadii.push(Math.round(effective)); } else squareMedia++;
348
+ } else if (rad >= 6 && r.width >= 150 && r.height >= 80 && (rgba(cs.backgroundColor).a > 0.04 || bt > 0)) { roundedBoxes++; boxRadii.push(Math.round(Math.min(rad, 99))); }
349
+ }
350
+ // elements that are themselves a thin rule (1px tall or 1px wide)
351
+ for (const el of all) {
352
+ const r = el.getBoundingClientRect();
353
+ const horiz = r.height > 0 && r.height <= 1.5 && r.width >= 160, vert = r.width > 0 && r.width <= 1.5 && r.height >= 160;
354
+ if (!horiz && !vert) continue; if (formish(el) && (el.tagName !== 'HR' || el.closest('[data-ui]'))) continue;
355
+ const cs = getComputedStyle(el); if (cs.display === 'none' || cs.visibility === 'hidden' || +cs.opacity < 0.03) continue;
356
+ if (rgba(cs.backgroundColor).a > 0.02 || parseFloat(cs.borderTopWidth) > 0 || parseFloat(cs.borderLeftWidth) > 0 || cs.backgroundImage !== 'none') { hairlines++; if (hairlineSamples.length < 5) hairlineSamples.push(label(el) + ' ' + Math.round(Math.max(r.width, r.height)) + 'px rule'); }
357
+ }
358
+
359
+ // --- repeated card rows (three or more same-size boxed siblings)
360
+ const cardRows = [];
361
+ const seenParents = new Set();
362
+ for (const b of big) {
363
+ const p = b.el.parentElement; if (!p || seenParents.has(p)) continue; seenParents.add(p);
364
+ // cards carry text; a row of photographs or media tiles is not a card row
365
+ const kids = [...p.children].filter((k) => { const r = k.getBoundingClientRect(); return r.width >= 150 && r.width < W * 0.9 && r.height >= 90 && !k.matches('img, picture, video, canvas, svg, figure:not(:has(figcaption))') && (k.innerText || '').trim().length >= 12; }); // full-width bands are sections, not cards
366
+ if (kids.length < 3) continue;
367
+ const boxed = kids.filter((k) => { const cs = getComputedStyle(k); return (rgba(cs.backgroundColor).a > 0.04 || parseFloat(cs.borderTopWidth) > 0 || cs.boxShadow !== 'none') && true; });
368
+ if (boxed.length < 3) continue;
369
+ const w0 = boxed[0].getBoundingClientRect().width;
370
+ const same = boxed.filter((k) => Math.abs(k.getBoundingClientRect().width - w0) < 3);
371
+ if (same.length < 3) continue;
372
+ const iconTop = same.filter((k) => { const first = k.querySelector('svg, img, i, [class*="icon" i]'); if (!first) return false; const r = first.getBoundingClientRect(); return r.width > 0 && r.width <= 64 && r.height <= 64; }).length;
373
+ cardRows.push({ count: same.length, iconTopped: iconTop >= same.length - 1 && iconTop >= 3, rounded: (parseFloat(getComputedStyle(same[0]).borderTopLeftRadius) || 0) >= 6, top: Math.round(same[0].getBoundingClientRect().top + SY) });
374
+ }
375
+
376
+ // --- hero media
377
+ const heroMedia = [];
378
+ for (const b of big) {
379
+ const { el, r, cs } = b; if (r.top > H * 0.9 || r.bottom < 0) continue;
380
+ const type = el.tagName === 'IMG' ? 'img' : el.tagName === 'VIDEO' ? 'video' : el.tagName === 'CANVAS' ? 'canvas' : el.tagName === 'svg' && r.width > 240 ? 'svg' : /url\(/.test(cs.backgroundImage) ? 'bg-image' : el.tagName === 'IFRAME' ? 'iframe' : null;
381
+ if (!type) continue;
382
+ const vw = Math.max(0, Math.min(r.right, W) - Math.max(r.left, 0)), vh = Math.max(0, Math.min(r.bottom, H) - Math.max(r.top, 0));
383
+ const ratio = (vw * vh) / (W * H); if (ratio >= 0.08) heroMedia.push({ type, ratio: round(ratio) });
384
+ }
385
+ heroMedia.sort((a, b) => b.ratio - a.ratio);
386
+ const brokenImages = [...document.images].filter((i) => i.complete && i.naturalWidth === 0 && i.getBoundingClientRect().width > 0 && (i.currentSrc || i.src)).map((i) => (i.currentSrc || i.src).slice(0, 120)).slice(0, 8);
387
+
388
+ // --- navigation
389
+ const navEl = document.querySelector('header nav, nav, header, [role="navigation"]');
390
+ let nav = null;
391
+ if (navEl) {
392
+ let host = navEl; let fixed = false; let p = navEl;
393
+ for (let i = 0; i < 4 && p && p !== document.body; i++, p = p.parentElement) { const pos = getComputedStyle(p).position; if (pos === 'fixed' || pos === 'sticky') { fixed = true; host = p; break; } }
394
+ const hr = host.getBoundingClientRect(); const hcs = getComputedStyle(host);
395
+ const links = [...navEl.querySelectorAll('a')].filter((a) => { const r = a.getBoundingClientRect(); return r.width > 0 && r.top < 140; });
396
+ nav = { links: links.length, fixed, height: Math.round(hr.height), floatingPill: hr.width < W * 0.92 && parseFloat(hcs.borderTopLeftRadius) >= 16 && hr.top > 4, blur: (hcs.backdropFilter || 'none') !== 'none' };
397
+ }
398
+
399
+ // data-ui is for cropped interface fragments, not for wrapping the page
400
+ const uiAbuse = [...document.querySelectorAll('[data-ui]')].some((el) => el.querySelector('h1') || el.getBoundingClientRect().height > H * 1.6 || el === document.body);
401
+ const uiFragments = document.querySelectorAll('[data-ui]').length;
402
+ // pictures that were not drawn by the page's own code: photographs, footage, raster artwork.
403
+ // Inline SVG and .svg files do not count; cropped interface fragments (data-ui) are counted apart.
404
+ const svgSrc = (u) => /\.svg(\?|#|$)|^data:image\/svg/i.test(u || '');
405
+ let realPictures = 0, videoCount = 0; const pictureSamples = [];
406
+ for (const el of all) {
407
+ if (el.closest('[data-ui]') || el.closest('svg')) continue;
408
+ const r = el.getBoundingClientRect(); if (r.width < 120 || r.height < 90 || r.width * r.height < 160 * 120) continue;
409
+ if (el.tagName === 'IMG') {
410
+ const src = el.currentSrc || el.src;
411
+ if (el.naturalWidth > 0 && !svgSrc(src)) { realPictures++; if (pictureSamples.length < 8) pictureSamples.push(src.split('/').pop().slice(0, 48)); }
412
+ continue;
413
+ }
414
+ if (el.tagName === 'VIDEO') { realPictures++; videoCount++; continue; }
415
+ const bg = getComputedStyle(el).backgroundImage;
416
+ if (bg && bg.includes('url(')) { const u = (bg.match(/url\(["']?([^"')]+)/) || [])[1]; if (!svgSrc(u)) realPictures++; }
417
+ }
418
+ const uiPictures = [...document.querySelectorAll('[data-ui]')].filter((el) => { const r = el.getBoundingClientRect(); return r.width >= 300 && r.height >= 200; }).length;
419
+ // which elements push the page wider than the viewport (the deepest ones that are not clipped by an ancestor)
420
+ let overflowCulprits = [];
421
+ if (document.documentElement.scrollWidth > W + 2) {
422
+ const clippedByAncestor = (el) => { for (let p = el.parentElement; p && p !== document.body; p = p.parentElement) { if (getComputedStyle(p).overflowX !== 'visible' && p.getBoundingClientRect().right <= W + 2) return true; } return false; };
423
+ const wide = all.filter((el) => { const r = el.getBoundingClientRect(); return r.width > 0 && r.right > W + 2 && !clippedByAncestor(el); });
424
+ overflowCulprits = wide.filter((el) => !wide.some((o) => o !== el && el.contains(o))).slice(0, 3).map((el) => `${el.tagName.toLowerCase()}${el.id ? '#' + el.id : ''}${el.classList.length ? '.' + [...el.classList].slice(0, 2).join('.') : ''} (+${Math.round(el.getBoundingClientRect().right - W)}px)`);
425
+ }
426
+ // rows that scroll sideways with content past their edge (read as clipped in screenshots and by most visitors)
427
+ const sideScrollers = all.filter((el) => { if (el.closest('[data-ui], table, pre, code')) return false; const cs = getComputedStyle(el); return /auto|scroll/.test(cs.overflowX) && el.scrollWidth > el.clientWidth + 24 && el.clientHeight > 40; }).length;
428
+ const blendText = texts.filter((t) => { for (let p = t.el, i = 0; p && p !== document.body && i < 6; p = p.parentElement, i++) if (/difference|exclusion/.test(getComputedStyle(p).mixBlendMode)) return true; return false; }).length;
429
+ const bodyText = document.body.innerText || '';
430
+ const emoji = (bodyText.match(/[\u{1F300}-\u{1FAFF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/gu) || []);
431
+ const doc = document.documentElement; const pageH = Math.max(doc.scrollHeight, document.body.scrollHeight);
432
+ const headingStats = headings.map(({ _el, ...h }) => h);
433
+ const heroOut = hero ? (({ _el, ...h }) => h)(hero) : null;
434
+ const below = headingStats.filter((h) => !heroOut || h.top !== heroOut.top);
435
+
436
+ return {
437
+ viewport: { w: W, h: H },
438
+ page: { height: pageH, screens: round(pageH / H, 1), words: bodyText.split(/\s+/).filter(Boolean).length,
439
+ img: document.images.length, video: document.querySelectorAll('video').length, canvas: document.querySelectorAll('canvas').length, svg: document.querySelectorAll('svg').length, sections: document.querySelectorAll('section').length,
440
+ horizontalOverflow: doc.scrollWidth > W + 2 ? doc.scrollWidth - W : 0, overflowCulprits, uiFragments, uiAbuse, realPictures, videoCount, uiPictures, pictureSamples, blendText, sideScrollers, brokenImages, emoji: [...new Set(emoji)].slice(0, 10),
441
+ videosNoPoster: [...document.querySelectorAll('video')].filter((v) => !v.getAttribute('poster') && v.getBoundingClientRect().width >= 160).length },
442
+ hero: heroOut, h1,
443
+ headings: headingStats.slice(0, 40),
444
+ headingSummary: { count: headingStats.length, maxLines: Math.max(0, ...headingStats.map((h) => h.lines)), over2Lines: headingStats.filter((h) => h.lines > 2 && h.kind !== 'statement').length, statements: headingStats.filter((h) => h.kind === 'statement').length, centered: headingStats.filter((h) => h.align === 'center').length, medianWords: below.length ? below.map((h) => h.words).sort((a, b) => a - b)[Math.floor(below.length / 2)] : null, mixedFontHeadings: headingStats.filter((h) => h.mixedFonts).length },
445
+ type: {
446
+ families, familyCount: famChars.size, sizes, sizeCount: sizes.length, notLoaded,
447
+ body: bodySample ? { font: bodySample.fam, size: bodySample.size, leading: bodySample.lh, weight: bodySample.weight, tracking: bodySample.ls, contrast: (() => { const bg = bgBehind(bodySample.el); return bg ? contrast(rgba(bodySample.color), bg) : null; })() } : null,
448
+ measureChars: longest ? Math.round(longest.w / (longest.size * 0.5)) : null,
449
+ max: maxText ? { size: maxText.size, vw: round((maxText.size / W) * 100, 1), font: maxText.fam, text: maxText.text.slice(0, 40) } : null,
450
+ monoShare: round(monoChars / Math.max(1, totalChars)), monoProseShare: round(monoProse / Math.max(1, totalChars)), monoFamilies: [...monoFams], monoSamples,
451
+ microLabels, microLabelShare: round(microLabelChars / Math.max(1, totalChars), 3), microSamples,
452
+ upperShare: round(upperChars / Math.max(1, totalChars)), centerShare: round(centerChars / Math.max(1, totalChars)), smallText,
453
+ },
454
+ eyebrows, numbered: numbered.slice(0, 12).map((t) => ({ text: t.text.slice(0, 30), size: t.size, top: Math.round(t.top) })),
455
+ color: {
456
+ bodyBg: hex(bodyBg), bodyBgHsl: hsl(bodyBg), dark: lum(bodyBg) < 0.18, sectionBgs: sectionBgs.slice(0, 10), textColors,
457
+ button: filledBtns.length ? { bg: mode(filledBtns.map((b) => b.bg)), hsl: mode(filledBtns.map((b) => b.bgHsl)), fg: mode(filledBtns.map((b) => b.fg)) } : null,
458
+ lowContrast: lowContrast.slice(0, 6),
459
+ },
460
+ buttons: { count: buttons.length, inHero: buttons.filter((b) => b.top < H).length, radius: mode(buttons.map((b) => b.radius)), height: mode(buttons.map((b) => b.h)), withShadow: buttons.filter((b) => b.shadow).length, withGradient: buttons.filter((b) => b.gradient).length, mono: buttons.filter((b) => b.mono).length, upper: buttons.filter((b) => b.upper).length, heroLabels: buttons.filter((b) => b.top < H).map((b) => b.text).filter(Boolean).slice(0, 5) },
461
+ surfaces: { shadowStatic, shadowOverlay, shadowSamples, gradients, scrims, gradientSamples, gradientText, hairlines, hairlineSamples, outlinedBoxes, dashed, blurred, roundedBoxes, boxRadius: mode(boxRadii) ?? 0, squareMedia, roundedMedia, mediaRadius: mode(mediaRadii) ?? 0, textShadow: texts.filter((t) => t.textShadow).length, cardRows: cardRows.slice(0, 8) },
462
+ heroMedia: heroMedia.slice(0, 4), nav,
463
+ fontsLoaded: [...loaded].slice(0, 12),
464
+ };
465
+ }
466
+
467
+ // frame() runs at each scroll position the inspector photographs. It reports large text that something
468
+ // else is lying on top of (a picture sliding over a headline), and how much of the screen holds anything.
469
+ export function frame() {
470
+ const W = innerWidth, H = innerHeight;
471
+ const pinnedTop = (el) => { for (let p = el; p && p !== document.body; p = p.parentElement) { const cs = getComputedStyle(p); if ((cs.position === 'fixed' || cs.position === 'sticky') && p.getBoundingClientRect().top < 140 && p.getBoundingClientRect().height < 180) return true; } return false; };
472
+ // 1. covered text. Hit-testing skips elements with pointer-events: none (a pinned title usually has it),
473
+ // which would make whatever lies beneath look as if it were on top; switch it on while sampling.
474
+ const hitStyle = document.createElement('style');
475
+ hitStyle.textContent = '*, *::before, *::after { pointer-events: auto !important; }';
476
+ document.head.appendChild(hitStyle);
477
+ const covered = [], clipped = [], pinnedOver = [];
478
+ const label = (el, direct) => (el.innerText || direct).replace(/\s+/g, ' ').trim().slice(0, 48);
479
+ const pinnedLow = (el) => { for (let p = el; p && p !== document.body; p = p.parentElement) { const pcs = getComputedStyle(p); if (pcs.position === 'fixed' || pcs.position === 'sticky') return !pinnedTop(el); } return false; };
480
+ // Does the element found on top actually paint anything there? A transparent layout box does not hide text.
481
+ const paints = (hit, el) => {
482
+ for (let p = hit; p && p !== document.body && !p.contains(el); p = p.parentElement) {
483
+ if (/^(IMG|VIDEO|CANVAS|PICTURE|IFRAME|svg)$/.test(p.tagName) || p.closest('svg')) return true;
484
+ const pcs = getComputedStyle(p);
485
+ if (pcs.backgroundImage !== 'none') return true;
486
+ const m = pcs.backgroundColor.match(/rgba?\(([^)]+)\)/); const parts = m ? m[1].split(/[,\s/]+/).filter(Boolean) : [];
487
+ if (parts.length && (parts.length < 4 || parseFloat(parts[3]) > 0.05)) return true;
488
+ if (p === hit) for (const n of p.childNodes) if (n.nodeType === 3 && n.nodeValue.trim()) return true; // its own text lies on ours
489
+ }
490
+ return false;
491
+ };
492
+ const pictures = [...document.querySelectorAll('img, picture, svg, canvas, video')].filter((m) => !m.parentElement?.closest('svg')).map((m) => ({ m, r: m.getBoundingClientRect() })).filter(({ r }) => r.width >= 80 && r.height >= 80 && r.bottom > 0 && r.top < H);
493
+ for (const el of document.querySelectorAll('h1, h2, h3, h4, h5, h6, p, blockquote, li, a, button, span, div')) {
494
+ let direct = ''; for (const n of el.childNodes) if (n.nodeType === 3) direct += n.nodeValue;
495
+ if (direct.trim().length < 3) continue;
496
+ const cs = getComputedStyle(el); const size = parseFloat(cs.fontSize); if (size < 24 || cs.visibility === 'hidden' || +cs.opacity < 0.3) continue;
497
+ let srOnly = false;
498
+ for (let p = el, i = 0; p && p !== document.body && i < 4; p = p.parentElement, i++) { const pcs = getComputedStyle(p); const pr = p.getBoundingClientRect(); if (pcs.clip === 'rect(0px, 0px, 0px, 0px)' || /inset\(50%\)/.test(pcs.clipPath) || (pr.width <= 2 && pr.height <= 2 && pcs.overflow !== 'visible')) { srOnly = true; break; } }
499
+ if (srOnly) continue;
500
+ // the boxes that clip this text (overflow other than visible, or a clip-path)
501
+ const clips = []; for (let p = el.parentElement; p && p !== document.body; p = p.parentElement) { const pcs = getComputedStyle(p); if (pcs.overflowY !== 'visible' || pcs.overflowX !== 'visible' || pcs.clipPath !== 'none') clips.push(p.getBoundingClientRect()); }
502
+ // a scroll scene moves pictures across its own held stage by design; its text is judged by the scene rules
503
+ const pinned = size >= 32 && pinnedLow(el) && !el.closest('.scene.is-live');
504
+ // lines of one heading set as separate spans overlap each other's glyph boxes; that is not a collision
505
+ const unit = el.closest('h1, h2, h3, h4, h5, h6, p, blockquote');
506
+ const range = document.createRange(); let total = 0, hidden = 0, cutTop = false, area = 0, over = 0;
507
+ for (const node of el.childNodes) {
508
+ if (node.nodeType !== 3 || !node.nodeValue.trim()) continue;
509
+ range.selectNodeContents(node);
510
+ for (const r of range.getClientRects()) {
511
+ if (r.width < 6 || r.bottom < 0 || r.top > H) continue;
512
+ // sampled at two heights inside the line box (a display face's glyph box often spills past it and
513
+ // under the next line), so a band lying over the top or the bottom of the letters is found too
514
+ const lh = parseFloat(cs.lineHeight); const band = Math.min(r.height, Number.isFinite(lh) ? lh : r.height);
515
+ for (const dy of [-0.2, 0.2]) for (const fx of [0.12, 0.37, 0.63, 0.88]) {
516
+ const x = r.left + r.width * fx, y = r.top + r.height * 0.55 + band * dy; if (x < 0 || x >= W || y < 0 || y >= H) continue;
517
+ total++; const hit = document.elementFromPoint(x, y);
518
+ if (hit && hit !== el && !el.contains(hit) && !hit.contains(el) && !pinnedTop(hit) && !(unit && unit.contains(hit) && !/^(IMG|VIDEO|CANVAS|PICTURE|svg)$/.test(hit.tagName) && !hit.closest('svg')) && paints(hit, el)) hidden++;
519
+ }
520
+ // the glyph box starts above the capitals (ascender room), so only a cut well into it is a real crop
521
+ if (size >= 40 && r.top >= -4 && clips.some((c) => c.top > r.top + r.height * 0.3 && c.top < r.bottom - r.height * 0.2)) cutTop = true;
522
+ if (pinned) { area += r.width * r.height; for (const { m, r: pr } of pictures) { if (m.contains(el) || el.contains(m)) continue; const ox = Math.min(r.right, pr.right) - Math.max(r.left, pr.left), oy = Math.min(r.bottom, pr.bottom) - Math.max(r.top, pr.top); if (ox > 0 && oy > 0) over += ox * oy; } }
523
+ }
524
+ }
525
+ if (total >= 4 && hidden / total >= 0.2) covered.push({ text: label(el, direct), share: Math.round((hidden / total) * 100) / 100 });
526
+ if (cutTop) clipped.push({ text: label(el, direct) });
527
+ if (pinned && area && over / area >= 0.15) pinnedOver.push({ text: label(el, direct), share: Math.min(1, Math.round((over / area) * 100) / 100) });
528
+ }
529
+ hitStyle.remove();
530
+ // 2. how much of the screen holds anything: text glyph boxes, media, or a ground that differs from the page's
531
+ const opaque = (c) => c && c !== 'transparent' && !/rgba\([^)]*,\s*0\)$/.test(c);
532
+ const pageBg = [document.body, document.documentElement].map((e) => getComputedStyle(e).backgroundColor).find(opaque) || 'rgb(255, 255, 255)';
533
+ const textRects = [];
534
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT); const rg = document.createRange();
535
+ for (let n = walker.nextNode(); n; n = walker.nextNode()) {
536
+ if (!n.nodeValue.trim() || !n.parentElement) continue; const pcs = getComputedStyle(n.parentElement); if (pcs.visibility === 'hidden' || +pcs.opacity < 0.2) continue;
537
+ rg.selectNodeContents(n); for (const r of rg.getClientRects()) if (r.bottom > 0 && r.top < H && r.width > 1) textRects.push(r);
538
+ }
539
+ let filled = 0, samples = 0;
540
+ for (let gx = 0; gx < 24; gx++) for (let gy = 0; gy < 15; gy++) {
541
+ const x = (gx + 0.5) * (W / 24), y = (gy + 0.5) * (H / 15); samples++;
542
+ if (textRects.some((r) => x >= r.left - 6 && x <= r.right + 6 && y >= r.top - 4 && y <= r.bottom + 4)) { filled++; continue; }
543
+ let el = document.elementFromPoint(x, y);
544
+ for (let i = 0; el && i < 6 && el !== document.body && el !== document.documentElement; i++, el = el.parentElement) {
545
+ if (/^(IMG|VIDEO|CANVAS|PICTURE|svg|IFRAME)$/.test(el.tagName) || el.closest('svg')) { filled++; break; }
546
+ const cs = getComputedStyle(el);
547
+ if (cs.backgroundImage !== 'none') { filled++; break; }
548
+ const bg = cs.backgroundColor; if (bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent') { if (bg !== pageBg) filled++; break; }
549
+ }
550
+ }
551
+ // 3. the large text in view, to spot one statement held on screen for screen after screen
552
+ const big = [...new Set([...document.querySelectorAll('h1, h2, h3, h4, h5, h6, p, blockquote')].filter((el) => { const r = el.getBoundingClientRect(); return !el.closest('.scene.is-live') && parseFloat(getComputedStyle(el).fontSize) >= 32 && r.top >= -4 && r.bottom <= H + 4 && r.height > 0; }).map((el) => (el.innerText || '').replace(/\s+/g, ' ').trim().slice(0, 60)).filter((t) => t.length >= 4))];
553
+ return { covered, clipped, pinnedOver, filled: Math.round((filled / samples) * 100) / 100, big, y: Math.round(scrollY) };
554
+ }
555
+
556
+ // layout() runs once, back at the top after the page has been scrolled through (so reveals have fired).
557
+ // It measures the frame of the page rather than its details: the two outer margins, sections whose content
558
+ // huddles on one side, blank stretches with nothing in them, the title, untitled sections, text pressed
559
+ // against the edge of the screen and how many left edges the text uses.
560
+ export function layout() {
561
+ const W = innerWidth, H = innerHeight, SY = scrollY;
562
+ const D = document.documentElement, B = document.body;
563
+ const pageH = Math.max(D.scrollHeight, B.scrollHeight);
564
+ const pct = (arr, p) => { if (!arr.length) return null; const s = [...arr].sort((a, b) => a - b); return s[Math.min(s.length - 1, Math.max(0, Math.round((s.length - 1) * p)))]; };
565
+ const shown = (el) => { for (let p = el, i = 0; p && p !== B && i < 30; p = p.parentElement, i++) { const cs = getComputedStyle(p); if (cs.display === 'none' || cs.visibility === 'hidden' || +cs.opacity < 0.05) return false; if (i < 4 && (cs.clip === 'rect(0px, 0px, 0px, 0px)' || /inset\(50%\)/.test(cs.clipPath))) return false; } return true; };
566
+ const layerOf = (el) => { for (let p = el; p && p !== B; p = p.parentElement) { const pos = getComputedStyle(p).position; if (pos === 'fixed') return 'fixed'; if (pos === 'sticky') return 'sticky'; } return null; };
567
+ const sel = (el) => (el.tagName.toLowerCase() + (el.id ? '#' + el.id : '') + (typeof el.className === 'string' && el.className.trim() ? '.' + el.className.trim().split(/\s+/).slice(0, 2).join('.') : '')).slice(0, 60);
568
+ const boxes = [];
569
+ // text, one box per text node (its line boxes merged)
570
+ const walker = document.createTreeWalker(B, NodeFilter.SHOW_TEXT); const rg = document.createRange();
571
+ for (let n = walker.nextNode(); n && boxes.length < 6000; n = walker.nextNode()) {
572
+ const t = n.nodeValue.replace(/\s+/g, ' ').trim(); if (t.length < 2) continue;
573
+ const el = n.parentElement; if (!el || /^(SCRIPT|STYLE|NOSCRIPT|TEMPLATE|OPTION)$/.test(el.tagName) || el.closest('svg')) continue;
574
+ rg.selectNodeContents(n); const r = rg.getBoundingClientRect(); if (r.width < 2 || r.height < 2) continue;
575
+ if (!shown(el)) continue;
576
+ const cs = getComputedStyle(el);
577
+ boxes.push({ kind: 'text', el, l: r.left, r: r.right, t: r.top + SY, b: r.bottom + SY, chars: t.length, text: t.slice(0, 50), size: parseFloat(cs.fontSize), align: cs.textAlign, layer: layerOf(el), tag: el.tagName });
578
+ }
579
+ // pictures, drawings and controls
580
+ for (const el of B.querySelectorAll('img, video, canvas, picture, iframe, svg, input, select, textarea, button, [role="button"], a, div, section, figure, span, li')) {
581
+ const tag = el.tagName;
582
+ let kind = null;
583
+ if (/^(IMG|VIDEO|CANVAS|PICTURE|IFRAME)$/.test(tag)) kind = 'media';
584
+ else if (tag === 'svg') { if (el.parentElement && el.parentElement.closest('svg')) continue; kind = 'media'; }
585
+ else if (/^(INPUT|SELECT|TEXTAREA|BUTTON)$/.test(tag) || el.getAttribute('role') === 'button') kind = 'control';
586
+ else { const bi = getComputedStyle(el).backgroundImage; if (bi && bi.includes('url(')) kind = 'media'; else continue; }
587
+ const r = el.getBoundingClientRect(); if (r.width < 24 || r.height < 24) continue;
588
+ if (kind === 'media' && r.width * r.height < 48 * 48) continue;
589
+ if (!shown(el)) continue;
590
+ boxes.push({ kind, el, l: r.left, r: r.right, t: r.top + SY, b: r.bottom + SY, chars: 0, size: 0, layer: layerOf(el), tag });
591
+ }
592
+ const flow = boxes.filter((b) => b.layer !== 'fixed');
593
+ // 1. the frame: how far the page's content comes to each edge (full-bleed things say nothing about margins)
594
+ // a row that runs off one edge (a carousel, a bled display line) says nothing about that side's margin
595
+ const framed = flow.filter((b) => b.r - b.l < W * 0.97 && b.l >= -1 && b.r <= W + 1);
596
+ const L = framed.length ? Math.max(0, pct(framed.map((b) => b.l), 0.02)) : null;
597
+ const R = framed.length ? Math.max(0, W - pct(framed.map((b) => b.r), 0.98)) : null;
598
+ // 2. sections: descend through single wrappers, then take the blocks
599
+ const tall = (c) => { const r = c.getBoundingClientRect(); return r.height >= 120 && r.width >= W * 0.6 && getComputedStyle(c).position !== 'fixed' && shown(c); };
600
+ let root = B;
601
+ for (let i = 0; i < 12; i++) { const kids = [...root.children].filter(tall); if (kids.length === 1) root = kids[0]; else break; }
602
+ // a tall block is split into its parts; the parts of a block that has its own heading continue it
603
+ let blocks = [...root.children].filter(tall).map((el) => ({ el, cont: false }));
604
+ for (let pass = 0; pass < 2; pass++) blocks = blocks.flatMap((b) => { const k = [...b.el.children].filter(tall); if (b.el.getBoundingClientRect().height > H * 1.6 && k.length >= 2) { const titled = b.cont || !!b.el.querySelector('h1, h2, h3, h4, [role="heading"]'); return k.map((el) => ({ el, cont: titled })); } return [b]; });
605
+ const continues = new Set(blocks.filter((b) => b.cont).map((b) => b.el));
606
+ blocks = blocks.map((b) => b.el);
607
+ const bodySize = (() => { const m = new Map(); for (const b of flow) if (b.kind === 'text' && b.chars >= 40) m.set(Math.round(b.size), (m.get(Math.round(b.size)) || 0) + b.chars); return [...m.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] || 16; })();
608
+ const sections = blocks.slice(0, 40).map((s, i) => {
609
+ const r = s.getBoundingClientRect(); const top = r.top + SY, bot = r.bottom + SY;
610
+ const inside = flow.filter((b) => b.t >= top - 2 && b.b <= bot + 2 && s.contains(b.el));
611
+ const own = inside.filter((b) => b.r - b.l < W * 0.97);
612
+ const text = inside.filter((b) => b.kind === 'text');
613
+ const media = inside.filter((b) => b.kind === 'media' && (b.r - b.l) * (b.b - b.t) >= 160 * 120);
614
+ const words = text.reduce((a, b) => a + b.text.split(' ').length + Math.max(0, Math.round((b.chars - 50) / 6)), 0);
615
+ const headRe = /^H[1-4]$/;
616
+ const heading = text.filter((b) => (b.el.closest('h1,h2,h3,h4,[role="heading"]') || b.size >= Math.max(22, bodySize * 1.35)) && b.t <= top + Math.max(520, (bot - top) * 0.45)).sort((a, b) => b.size - a.size)[0];
617
+ const cs = getComputedStyle(s);
618
+ // the widest stretch across the section's content band that nothing covers, edge to edge excluded
619
+ let middle = 0;
620
+ if (own.length >= 2) {
621
+ const iv = own.map((b) => [Math.max(0, b.l), Math.min(W, b.r)]).sort((a, b) => a[0] - b[0]);
622
+ let end = iv[0][1];
623
+ for (const [a, b2] of iv.slice(1)) { if (a > end) middle = Math.max(middle, a - end); end = Math.max(end, b2); }
624
+ }
625
+ return {
626
+ sel: sel(s), tag: s.tagName.toLowerCase(), top: Math.round(top), height: Math.round(r.height), words, media: media.length, middle: Math.round(middle),
627
+ heading: heading ? { text: heading.text, size: Math.round(heading.size), tag: (heading.el.closest('h1,h2,h3,h4,h5,h6') || heading.el).tagName.toLowerCase() } : null,
628
+ left: own.length ? Math.round(Math.max(0, Math.min(...own.map((b) => b.l)))) : null, right: own.length ? Math.round(Math.max(0, W - Math.max(...own.map((b) => b.r)))) : null,
629
+ textLeft: text.length ? Math.round(Math.min(...text.map((b) => b.l))) : null, textRight: text.length ? Math.round(W - Math.max(...text.map((b) => b.r))) : null,
630
+ fullBleed: inside.some((b) => b.kind === 'media' && b.r - b.l >= W * 0.97), footer: s.tagName === 'FOOTER' || !!s.querySelector('footer') || i === blocks.length - 1,
631
+ hero: top < H * 0.5, continues: continues.has(s), pinned: cs.position === 'sticky' || !!s.querySelector('.pin-spacer') || [...s.querySelectorAll('*')].slice(0, 400).some((e) => getComputedStyle(e).position === 'sticky' && e.getBoundingClientRect().height > H * 0.5),
632
+ };
633
+ });
634
+ // 3. blank stretches: rows of the page with no text, picture or control, between the first and the last content.
635
+ // A sticky element's track (its parent) and a pinning spacer are scroll distance, not blank page.
636
+ const STEP = 4, rows = Math.ceil(pageH / STEP), occ = new Uint8Array(rows);
637
+ const mark = (t, b, v) => { for (let y = Math.max(0, Math.floor(t / STEP)); y < Math.min(rows, Math.ceil(b / STEP)); y++) occ[y] = v; };
638
+ for (const b of flow) if (b.layer !== 'sticky') mark(b.t, b.b, 1);
639
+ for (const el of B.querySelectorAll('.pin-spacer')) { const r = el.getBoundingClientRect(); mark(r.top + SY, r.bottom + SY, 2); }
640
+ let scanned = 0;
641
+ for (const el of B.querySelectorAll('*')) { if (++scanned > 9000) break; const cs = getComputedStyle(el); if (cs.position === 'sticky' && el.parentElement) { const r = el.parentElement.getBoundingClientRect(); mark(r.top + SY, r.bottom + SY, 2); } }
642
+ let first = 0; while (first < rows && occ[first] !== 1) first++;
643
+ let last = rows - 1; while (last > 0 && occ[last] !== 1) last--;
644
+ const gaps = []; let run = 0;
645
+ for (let y = first; y <= last + 1; y++) { if (y <= last && occ[y] === 0) run++; else { if (run * STEP >= H * 0.3) gaps.push({ top: (y - run) * STEP, height: run * STEP }); run = 0; } }
646
+ // 4. the title
647
+ const h1s = [...document.querySelectorAll('h1')].filter((h) => { const r = h.getBoundingClientRect(); return r.width > 0 && r.height > 0 && shown(h); }).map((h) => { const r = h.getBoundingClientRect(); return { text: (h.innerText || '').replace(/\s+/g, ' ').trim().slice(0, 80), size: Math.round(parseFloat(getComputedStyle(h).fontSize)), top: Math.round(r.top + SY), firstScreen: r.top + SY < H * 0.95 }; });
648
+ // 5. text pressed against the edge of the screen (type deliberately bled past the edge is a different device)
649
+ // fixed headers and corner labels count here: that is where text most often creeps to the edge
650
+ const edge = 16;
651
+ const edgeText = boxes.filter((b) => b.kind === 'text' && b.chars >= 3 && b.size < 40 && b.l >= -1 && b.r <= W + 1 && (b.l < edge || b.r > W - edge) && !b.el.closest('[class*="marquee" i],[class*="ticker" i],marquee,[data-ui],table')).slice(0, 8).map((b) => ({ text: b.text, side: b.l < edge ? 'left' : 'right', px: Math.round(b.l < edge ? b.l : W - b.r) }));
652
+ // display type that runs off one edge by a sliver while it starts inside the margin: a fit error, not a bleed
653
+ const slivers = boxes.filter((b) => b.kind === 'text' && b.size >= 40 && b.chars >= 3 && ((b.l >= 8 && b.r > W + 1 && b.r - W < b.size * 0.25) || (b.r <= W - 8 && b.l < -1 && -b.l < b.size * 0.25))).slice(0, 4).map((b) => ({ text: b.text.slice(0, 30), px: Math.round(b.r > W ? b.r - W : -b.l), side: b.r > W ? 'right' : 'left' }));
654
+ // the largest empty rectangle on the first screen (below a 90px band for the navigation), on a 24 x 15 grid
655
+ let voidShare = 0, voidBox = null;
656
+ if (W >= 1000) {
657
+ const C = 24, Rn = 15, cw = W / C, ch = H / Rn, occ2 = Array.from({ length: Rn }, () => new Array(C).fill(0));
658
+ for (const b of boxes) { if (b.t - SY > H || b.b - SY < 0) continue; const c0 = Math.max(0, Math.floor(b.l / cw)), c1 = Math.min(C - 1, Math.floor((b.r - 1) / cw)), r0 = Math.max(0, Math.floor((b.t - SY) / ch)), r1 = Math.min(Rn - 1, Math.floor((b.b - SY - 1) / ch)); for (let r = r0; r <= r1; r++) for (let c = c0; c <= c1; c++) occ2[r][c] = 1; }
659
+ const top = Math.ceil(90 / ch), hgt = new Array(C).fill(0);
660
+ for (let r = top; r < Rn; r++) {
661
+ for (let c = 0; c < C; c++) hgt[c] = occ2[r][c] ? 0 : hgt[c] + 1;
662
+ for (let c = 0; c < C; c++) { let mn = Infinity; for (let k = c; k < C && hgt[k]; k++) { mn = Math.min(mn, hgt[k]); const a = mn * (k - c + 1); if (a > voidShare) { voidShare = a; voidBox = { x: Math.round(c * cw), y: Math.round((r - mn + 1) * ch), w: Math.round((k - c + 1) * cw), h: Math.round(mn * ch) }; } } }
663
+ }
664
+ voidShare = Math.round((voidShare / (C * Rn)) * 100) / 100;
665
+ }
666
+ // a fixed picture layer that covers the screen (a canvas scene, a film) hides what the DOM cannot see
667
+ const stage = boxes.some((b) => b.layer === 'fixed' && b.kind === 'media' && (b.r - b.l) * (b.b - b.t) >= W * H * 0.9);
668
+ // the first screen: is the title a bare name, with nothing at reading size saying what this is?
669
+ const firstText = flow.filter((b) => b.kind === 'text' && b.t < H * 0.95 && b.size >= 18 && !b.el.closest('h1, nav, header nav, button, a'));
670
+ const explains = firstText.some((b) => b.text.split(' ').length >= 6 || b.chars >= 36);
671
+ // 6. left edges used by running text and headings (left-aligned blocks only)
672
+ // headings and paragraphs only: the items of a grid or a list each start their own column on purpose
673
+ const lefts = flow.filter((b) => b.kind === 'text' && b.size >= 14 && !/center|right|end/.test(b.align) && b.l >= 0 && (b.el.closest('h1, h2, h3') || (b.chars >= 60 && b.el.closest('p, blockquote'))) && !b.el.closest('nav, footer, [data-ui], table, li, figcaption')).map((b) => b.l).sort((a, b) => a - b);
674
+ const edges = []; for (const x of lefts) { const e = edges.find((g) => Math.abs(g.x - x) <= 10); if (e) e.n++; else edges.push({ x: Math.round(x), n: 1 }); }
675
+ edges.sort((a, b) => b.n - a.n);
676
+ return { viewport: { w: W, h: H }, pageH, margins: { left: L == null ? null : Math.round(L), right: R == null ? null : Math.round(R) }, sections, gaps, h1s, edgeText, edges: edges.slice(0, 12), leftBlocks: lefts.length, bodySize, stage, explains, slivers, voidShare, voidBox };
677
+ }
678
+
679
+ // app() measures what an app screen (dashboard, admin, settings, inbox) gets right or wrong: the shell,
680
+ // how much of the canvas is used, panels and their edges, numbers in tables and KPIs, chart colours,
681
+ // the icon-in-a-tinted-square habit, placeholder data, and what happens to the sidebar on a phone.
682
+ export function app() {
683
+ const W = innerWidth, H = innerHeight, SY = scrollY, B = document.body;
684
+ const cvs = document.createElement('canvas'); cvs.width = cvs.height = 1;
685
+ const ctx = cvs.getContext('2d', { willReadFrequently: true });
686
+ const rgba = (c) => { ctx.clearRect(0, 0, 1, 1); ctx.fillStyle = 'rgba(0,0,0,0)'; ctx.fillStyle = c; ctx.fillRect(0, 0, 1, 1); const d = ctx.getImageData(0, 0, 1, 1).data; return { r: d[0], g: d[1], b: d[2], a: d[3] / 255 }; };
687
+ const hex = (v) => '#' + [v.r, v.g, v.b].map((n) => n.toString(16).padStart(2, '0')).join('');
688
+ const lum = (v) => { const f = (x) => { x /= 255; return x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4; }; return 0.2126 * f(v.r) + 0.7152 * f(v.g) + 0.0722 * f(v.b); };
689
+ const contrast = (a, b) => { const l1 = lum(a), l2 = lum(b); return Math.round(((Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05)) * 100) / 100; };
690
+ const hsl = (v) => { const r = v.r / 255, g = v.g / 255, b = v.b / 255, mx = Math.max(r, g, b), mn = Math.min(r, g, b); const l = (mx + mn) / 2; let h = 0, s = 0; if (mx !== mn) { const d = mx - mn; s = l > 0.5 ? d / (2 - mx - mn) : d / (mx + mn); h = mx === r ? (g - b) / d + (g < b ? 6 : 0) : mx === g ? (b - r) / d + 2 : (r - g) / d + 4; h *= 60; } return { h: Math.round(h), s, l }; };
691
+ const shown = (el, cs) => { cs = cs || getComputedStyle(el); if (cs.display === 'none' || cs.visibility === 'hidden' || +cs.opacity < 0.05) return false; const r = el.getBoundingClientRect(); return r.width > 0 && r.height > 0; };
692
+ const groundOf = (el) => { for (let p = el; p; p = p.parentElement) { const v = rgba(getComputedStyle(p).backgroundColor); if (v.a >= 0.9) return v; } return { r: 255, g: 255, b: 255, a: 1 }; };
693
+ const pageBg = groundOf(B);
694
+ const all = [...B.querySelectorAll('*')].slice(0, 12000);
695
+ // 1. the shell: a tall column at one side (sidebar) and a bar across the top
696
+ let sidebar = null, topbar = null;
697
+ for (const el of all) {
698
+ const r = el.getBoundingClientRect(); if (r.width < 40) continue;
699
+ if (!sidebar && r.left <= 1 && r.top <= 80 && r.height >= H * 0.7 && r.width >= 48 && r.width <= Math.min(360, W * 0.45) && (el.querySelectorAll('a, button').length >= 3) && shown(el)) sidebar = { w: Math.round(r.width), tag: el.tagName.toLowerCase(), links: el.querySelectorAll('a, button').length, share: Math.round((r.width / W) * 100) / 100 };
700
+ if (!topbar && r.top <= 1 && r.left <= (sidebar ? sidebar.w + 2 : 2) && r.width >= W * 0.6 && r.height >= 36 && r.height <= 96 && shown(el)) topbar = { h: Math.round(r.height) };
701
+ if (sidebar && topbar) break;
702
+ }
703
+ // 2. how much of the canvas the content uses (right of the sidebar)
704
+ const x0 = sidebar ? sidebar.w : 0;
705
+ // the text itself (a block element's box runs to the edge whatever its text does), pictures, charts and controls
706
+ const boxes = [];
707
+ const tw = document.createTreeWalker(B, NodeFilter.SHOW_TEXT); const trg = document.createRange();
708
+ for (let n = tw.nextNode(); n && boxes.length < 5000; n = tw.nextNode()) {
709
+ if (n.nodeValue.trim().length < 2 || !n.parentElement || n.parentElement.closest('svg')) continue;
710
+ const cs = getComputedStyle(n.parentElement); if (cs.visibility === 'hidden' || cs.display === 'none' || +cs.opacity < 0.05) continue;
711
+ trg.selectNodeContents(n); const r = trg.getBoundingClientRect(); if (r.width < 2 || r.left < x0 - 1 || (cs.position === 'fixed' && r.left > W * 0.5)) continue;
712
+ boxes.push(r);
713
+ }
714
+ for (const el of B.querySelectorAll('svg, canvas, img, video, input, select, textarea, button, table')) {
715
+ if (el.parentElement && el.parentElement.closest('svg')) continue;
716
+ const r = el.getBoundingClientRect(); if (r.width < 8 || r.height < 8 || r.left < x0 - 1 || !shown(el)) continue;
717
+ boxes.push(r);
718
+ }
719
+ const rights = boxes.map((r) => r.right).sort((a, b) => a - b), lefts = boxes.map((r) => r.left).sort((a, b) => a - b);
720
+ const canvas = boxes.length ? { left: Math.round(lefts[Math.floor(lefts.length * 0.02)] - x0), right: Math.round(W - rights[Math.floor(rights.length * 0.98)]) } : null;
721
+ // 3. panels: boxes that read as cards (a fill one step from the ground, a border, or a shadow)
722
+ const panels = []; const radii = new Map(); let heavyBorders = 0, heavyShadows = 0, softShadows = 0, glass = 0; const borderSamples = [], shadowSamples = [];
723
+ for (const el of all) {
724
+ const r = el.getBoundingClientRect(); if (r.width < 140 || r.height < 72 || r.width > W * 0.98) continue;
725
+ const cs = getComputedStyle(el); if (!shown(el, cs)) continue;
726
+ const bg = rgba(cs.backgroundColor), bw = parseFloat(cs.borderTopWidth) || 0, bc = rgba(cs.borderTopColor);
727
+ const ground = groundOf(el.parentElement || B);
728
+ const filled = bg.a >= 0.9 && contrast(bg, ground) >= 1.04, bordered = bw >= 0.5 && cs.borderTopStyle !== 'none' && bc.a > 0.05;
729
+ const sh = cs.boxShadow !== 'none' ? cs.boxShadow : '';
730
+ if (!filled && !bordered && !sh) continue;
731
+ if (!(el.innerText || '').trim()) continue;
732
+ panels.push({ el, w: Math.round(r.width), h: Math.round(r.height), top: Math.round(r.top + SY), left: Math.round(r.left), bottom: Math.round(r.bottom + SY), right: Math.round(r.right) });
733
+ const rad = Math.round(parseFloat(cs.borderTopLeftRadius) || 0); radii.set(rad, (radii.get(rad) || 0) + 1);
734
+ if (bordered) { const c = contrast({ ...bc, a: 1 }, bg.a >= 0.9 ? bg : ground); if (c >= (lum(ground) > 0.4 ? 1.45 : 1.9)) { heavyBorders++; if (borderSamples.length < 3) borderSamples.push(`${hex(bc)} on ${hex(bg.a >= 0.9 ? bg : ground)} (${c}:1)`); } }
735
+ if (sh) {
736
+ for (const part of sh.split(/,(?![^(]*\))/)) {
737
+ if (/inset/.test(part)) continue; const col = part.match(/rgba?\([^)]+\)/); const nums = part.replace(/rgba?\([^)]+\)/, '').trim().split(/\s+/).map(parseFloat);
738
+ const a = col ? rgba(col[0]).a : 1; const blur = nums[2] || 0, y = Math.abs(nums[1] || 0);
739
+ if (a <= 0.02 || (blur === 0 && y === 0 && !(nums[3] > 0))) continue;
740
+ // on a dark ground a black shadow barely shows; there it only counts when it is near-opaque
741
+ const darkGround = lum(ground) < 0.2;
742
+ if (darkGround ? a >= 0.45 : ((a >= 0.12 && blur >= 12) || y >= 8 || a >= 0.25)) { heavyShadows++; if (shadowSamples.length < 3) shadowSamples.push(part.trim().slice(0, 70)); } else softShadows++;
743
+ }
744
+ }
745
+ if (cs.backdropFilter && cs.backdropFilter !== 'none') glass++;
746
+ }
747
+ // top-level panels only (a card inside a panel is part of it): rows that end ragged, and how many
748
+ // panels start on the first screen
749
+ const panelEls = new Set(panels.map((p) => p.el));
750
+ const tops = panels.filter((p) => { for (let a = p.el.parentElement; a && a !== B; a = a.parentElement) if (panelEls.has(a)) return false; return p.w < W * 0.97 || p.h < H * 2; });
751
+ // a column of cards stacked with a gutter of 24px or less is one block: two cards on the left that end level with
752
+ // one tall card on the right are not ragged. A pair is ragged only when the cards and their columns both end apart.
753
+ const below = (p) => tops.find((q) => q !== p && Math.abs(q.left - p.left) <= 4 && Math.abs(q.w - p.w) <= 4 && q.top >= p.bottom - 2 && q.top - p.bottom <= 24);
754
+ const columnBottom = (p) => { let b = p.bottom; for (let q = below(p), n = 0; q && n < 12; q = below(q), n++) b = q.bottom; return b; };
755
+ const ragged = [];
756
+ for (let i = 0; i < tops.length; i++) for (let k = i + 1; k < tops.length; k++) {
757
+ const a = tops[i], b = tops[k];
758
+ if (Math.abs(a.top - b.top) > 4 || a.w < 200 || b.w < 200 || (a.right > b.left - 4 && b.right > a.left - 4)) continue; // side by side, same row
759
+ const diff = Math.min(Math.abs(a.bottom - b.bottom), Math.abs(columnBottom(a) - columnBottom(b))), tall = Math.max(a.h, b.h);
760
+ if (diff > Math.max(24, tall * 0.12)) ragged.push({ diff, top: a.top });
761
+ }
762
+ // the largest blank band inside a card: the vertical gap between consecutive pieces of its content (text, charts,
763
+ // pictures, controls, rules, fills), or from the last piece to the card's padding edge. A legend pinned to the foot
764
+ // of a stretched card, or four rows in a card kept at its neighbour's height, leave one.
765
+ const voids = [];
766
+ const seen = new Map();
767
+ const visibleIn = (el, root) => { if (seen.has(el)) return seen.get(el); let ok = true; for (let p = el; p && p !== root.parentElement; p = p.parentElement) { const cs = getComputedStyle(p); if (cs.display === 'none' || cs.visibility === 'hidden' || +cs.opacity < 0.05 || cs.position === 'fixed') { ok = false; break; } } seen.set(el, ok); return ok; };
768
+ for (const p of tops) {
769
+ if (p.el.matches('nav, aside, header, footer, dialog, [role="navigation"], [role="dialog"]') || p.el.querySelector('nav') || (sidebar && p.left < sidebar.w - 4) || p.h < 120) continue;
770
+ if (panels.filter((q) => q.el !== p.el && p.el.contains(q.el)).length >= 2) continue; // a frame of cards, not a card
771
+ const cs = getComputedStyle(p.el), pr = p.el.getBoundingClientRect();
772
+ const inTop = pr.top + (parseFloat(cs.borderTopWidth) || 0) + (parseFloat(cs.paddingTop) || 0), inBottom = pr.bottom - (parseFloat(cs.borderBottomWidth) || 0) - (parseFloat(cs.paddingBottom) || 0);
773
+ const bg = rgba(cs.backgroundColor), spans = [];
774
+ const tw2 = document.createTreeWalker(p.el, NodeFilter.SHOW_TEXT), rg = document.createRange();
775
+ // text that opens a section (a heading, a summary, or text at 14px/600 or 15px/500 and up) may follow a wider, placed gap
776
+ for (let n = tw2.nextNode(); n; n = tw2.nextNode()) { if (!n.nodeValue.trim() || !n.parentElement || n.parentElement.closest('svg') || !visibleIn(n.parentElement, p.el)) continue; rg.selectNodeContents(n); const r = rg.getBoundingClientRect(); if (r.height > 0 && r.width > 0) { const pcs = getComputedStyle(n.parentElement); const fs = parseFloat(pcs.fontSize), fw = +pcs.fontWeight; spans.push([r.top, r.bottom, !!n.parentElement.closest('h1, h2, h3, h4, h5, h6, [role="heading"], summary, legend, caption') || (fw >= 600 && fs >= 14) || (fw >= 500 && fs >= 15)]); } }
777
+ for (const el of p.el.querySelectorAll('*')) {
778
+ if (el.parentElement && el.parentElement.closest('svg')) continue;
779
+ const r = el.getBoundingClientRect(); if (r.width < 2 || r.height < 1 || !visibleIn(el, p.el)) continue;
780
+ if (/^(svg|canvas|img|video|input|select|textarea|button|hr|progress|meter|iframe)$/i.test(el.tagName)) { spans.push([r.top, r.bottom]); continue; }
781
+ const ecs = getComputedStyle(el), fillC = rgba(ecs.backgroundColor);
782
+ if (fillC.a >= 0.1 && (bg.a < 0.9 || contrast({ ...fillC, a: 1 }, bg) >= 1.03) && r.height >= 2) spans.push([r.top, r.bottom]);
783
+ if ((parseFloat(ecs.borderTopWidth) || 0) >= 0.5 && ecs.borderTopStyle !== 'none' && rgba(ecs.borderTopColor).a > 0.05) spans.push([r.top, r.top + 1]);
784
+ if ((parseFloat(ecs.borderBottomWidth) || 0) >= 0.5 && ecs.borderBottomStyle !== 'none' && rgba(ecs.borderBottomColor).a > 0.05) spans.push([r.bottom - 1, r.bottom]);
785
+ }
786
+ const ys = spans.filter(([a, b]) => b > inTop - 2 && a < inBottom + 2).sort((a, b) => a[0] - b[0]);
787
+ if (!ys.length) continue;
788
+ // a band counts when it is over 40px, or over 64px when the content after it opens a new section
789
+ let end = ys[0][1], gap = 0, at = null;
790
+ for (const [a, b] of ys.slice(1)) { const g = a - end, section = ys.some(([t, , hd]) => hd && Math.abs(t - a) < 6); if (g > (section ? 64 : 40) && g > gap) { gap = g; at = end; } end = Math.max(end, b); }
791
+ if (inBottom - end > 40 && inBottom - end > gap) { gap = inBottom - end; at = end; }
792
+ const head = (p.el.querySelector('h1, h2, h3, h4, [role="heading"], header') || p.el).innerText || '';
793
+ if (gap > 40) voids.push({ gap: Math.round(gap), at: Math.round(at + SY), panel: head.trim().split('\n')[0].slice(0, 40) });
794
+ }
795
+ voids.sort((a, b) => b.gap - a.gap);
796
+ const firstScreen = tops.filter((p) => p.top < H - 48).length;
797
+ // the icon-in-a-tinted-square habit: small boxes with a coloured fill holding one icon, repeated
798
+ const chips = []; let tiles = 0;
799
+ for (const el of all) {
800
+ const r = el.getBoundingClientRect(); if (r.width < 24 || r.width > 64 || Math.abs(r.width - r.height) > 4) continue;
801
+ const cs = getComputedStyle(el); const bg = rgba(cs.backgroundColor); if (bg.a < 0.08) continue;
802
+ const icon = el.querySelector('svg, i, img, [class*="icon" i]'); if (!icon || (el.innerText || '').trim().length > 2) continue;
803
+ const c = hsl(bg); if (c.s < 0.18 && bg.a > 0.5) { if (r.width >= 28 && !el.closest('button, a, nav, aside, [role="navigation"]')) tiles++; continue; } // neutral: an avatar, a button, or a boxed icon
804
+ chips.push({ hue: c.h, size: Math.round(r.width), radius: cs.borderTopLeftRadius });
805
+ }
806
+ const chipHues = new Set(chips.map((c) => Math.round(c.hue / 30)));
807
+ // 5. numbers: tables and KPI figures
808
+ const numRe = /^[−\-+]?[$€£¥]?\s?\d[\d,.\s]*(%|[kKmMbB]|\s?(ms|s|h|min|GB|MB|kB|x))?$/;
809
+ const cells = [...B.querySelectorAll('td, [role="cell"], [role="gridcell"]')].filter((el) => numRe.test((el.innerText || '').trim()));
810
+ const leftNums = cells.filter((el) => { const a = getComputedStyle(el).textAlign; const inner = el.firstElementChild ? getComputedStyle(el.firstElementChild).textAlign : a; return /left|start/.test(a) && /left|start/.test(inner) && getComputedStyle(el).justifyContent !== 'flex-end'; }).length;
811
+ const figures = [];
812
+ for (const el of all) { if (el.children.length) continue; const t = (el.innerText || '').trim(); if (!numRe.test(t) || t.length < 2) continue; const cs = getComputedStyle(el); if (!shown(el, cs)) continue; figures.push({ size: parseFloat(cs.fontSize), tabular: /tabular-nums/.test(cs.fontVariantNumeric) || /"tnum"/.test(cs.fontFeatureSettings), weight: +cs.fontWeight }); }
813
+ const tabularShare = figures.length ? Math.round((figures.filter((f) => f.tabular).length / figures.length) * 100) / 100 : null;
814
+ const rows = [...B.querySelectorAll('tbody tr, [role="row"]')].map((tr) => Math.round(tr.getBoundingClientRect().height)).filter((h) => h > 0);
815
+ // 6. chart colours: saturated fills and strokes inside SVG charts and canvas-free charts
816
+ const hues = new Map();
817
+ for (const svg of B.querySelectorAll('svg')) {
818
+ const r = svg.getBoundingClientRect(); if (r.width < 120 || r.height < 60) continue;
819
+ for (const s of svg.querySelectorAll('path, rect, circle, line, polyline, polygon')) {
820
+ const cs = getComputedStyle(s);
821
+ for (const c of [cs.fill, cs.stroke]) { if (!c || c === 'none' || c.startsWith('url')) continue; const v = rgba(c); if (v.a < 0.3) continue; const h = hsl(v); if (h.s < 0.25 || h.l < 0.12 || h.l > 0.92) continue; const k = Math.round(h.h / 24); hues.set(k, (hues.get(k) || 0) + 1); }
822
+ }
823
+ }
824
+ // bars and segments drawn with divs count too (a common way to fake a chart)
825
+ // 7. placeholder data
826
+ const text = B.innerText || '';
827
+ const placeholders = [...new Set((text.match(/\b(John|Jane) Doe\b|\bAcme( Inc| Corp)?\b|\bLorem ipsum\b|\bexample\.com\b|\bUser Name\b|\bFirstname Lastname\b|\b123 Main St\b|\bfoo(bar)?\b/gi) || []).map((s) => s.toLowerCase()))];
828
+ // 8. type at app sizes
829
+ const sizes = new Map(); let tiny = 0; const tinySamples = [];
830
+ const walker = document.createTreeWalker(B, NodeFilter.SHOW_TEXT);
831
+ for (let n = walker.nextNode(); n; n = walker.nextNode()) { const t = n.nodeValue.trim(); if (t.length < 2 || !n.parentElement) continue; const cs = getComputedStyle(n.parentElement); if (cs.visibility === 'hidden' || cs.display === 'none') continue; const s = Math.round(parseFloat(cs.fontSize)); sizes.set(s, (sizes.get(s) || 0) + t.length); if (s < 11 && t.length >= 3) { tiny++; if (tinySamples.length < 4) tinySamples.push(t.slice(0, 24) + ' ' + s + 'px'); } }
832
+ const total = [...sizes.values()].reduce((a, b) => a + b, 0) || 1;
833
+ const under13 = Math.round(([...sizes.entries()].filter(([s]) => s < 13).reduce((a, [, c]) => a + c, 0) / total) * 100) / 100;
834
+ const h1 = document.querySelector('h1'); const h1r = h1 && h1.getBoundingClientRect();
835
+ // the screen's hero: its h1 when that is on the first screen, else the largest text there. Text is read with a
836
+ // space between element children, so a figure and its unit ("£55.94" and "at cost") do not run together.
837
+ const spaced = (el) => { const out = []; const w = document.createTreeWalker(el, NodeFilter.SHOW_TEXT); for (let n = w.nextNode(); n; n = w.nextNode()) { const t = n.nodeValue.replace(/\s+/g, ' ').trim(); if (t && n.parentElement && !n.parentElement.closest('svg, script, style') && getComputedStyle(n.parentElement).display !== 'none') out.push(t); } return out.join(' ').replace(/\s+([,.;:!?%])/g, '$1').trim(); };
838
+ let heroEl = h1 && h1r && h1r.width > 0 && h1r.top + SY < H * 0.95 ? h1 : null;
839
+ if (!heroEl) {
840
+ let best = null; const hw = document.createTreeWalker(B, NodeFilter.SHOW_TEXT);
841
+ for (let n = hw.nextNode(); n; n = hw.nextNode()) {
842
+ const el = n.parentElement; if (!el || n.nodeValue.trim().length < 2 || el.closest('svg, nav, aside, [role="navigation"]')) continue;
843
+ const r = el.getBoundingClientRect(); if (r.top + SY > H * 0.95 || r.width < 1 || (sidebar && r.left < sidebar.w - 2)) continue;
844
+ const cs = getComputedStyle(el); if (cs.visibility === 'hidden' || +cs.opacity < 0.05) continue;
845
+ const size = parseFloat(cs.fontSize); if (!best || size > best.size) best = { el, size };
846
+ }
847
+ if (best) { heroEl = best.el; for (let p = heroEl; p && p !== B; p = p.parentElement) if (!/^inline/.test(getComputedStyle(p).display)) { heroEl = p; break; } }
848
+ }
849
+ let hero = null;
850
+ if (heroEl) {
851
+ const big = [...heroEl.querySelectorAll('*'), heroEl].map((e) => ({ e, s: parseFloat(getComputedStyle(e).fontSize) })).filter((o) => [...o.e.childNodes].some((c) => c.nodeType === 3 && c.nodeValue.trim())).sort((a, b) => b.s - a.s)[0]?.e || heroEl;
852
+ const cs = getComputedStyle(big), size = parseFloat(cs.fontSize), txt = spaced(heroEl), r = heroEl.getBoundingClientRect();
853
+ const tops2 = []; const lw = document.createTreeWalker(heroEl, NodeFilter.SHOW_TEXT); const lr = document.createRange();
854
+ for (let n = lw.nextNode(); n; n = lw.nextNode()) { if (!n.nodeValue.trim()) continue; lr.selectNodeContents(n); for (const rc of lr.getClientRects()) { const mid = rc.top + rc.height / 2; if (rc.width >= 1 && !tops2.some((t) => Math.abs(t - mid) < size * 0.55)) tops2.push(mid); } }
855
+ const lh = parseFloat(cs.lineHeight);
856
+ hero = { text: txt.slice(0, 200), words: txt.split(' ').filter(Boolean).length, chars: txt.length, lines: Math.max(1, tops2.length), size: Math.round(size), vw: Math.round((size / W) * 1000) / 10, font: cs.fontFamily.split(',')[0].replace(/["']/g, '').trim(), weight: +cs.fontWeight, tracking: Math.round(((parseFloat(cs.letterSpacing) || 0) / size) * 100) / 100, leading: Number.isFinite(lh) ? Math.round((lh / size) * 100) / 100 : 1.2, top: Math.round(r.top + SY), tag: heroEl.tagName.toLowerCase(), kind: 'heading', align: 'left', position: 'left' };
857
+ }
858
+ // charts and timelines on the screen (for the data-hue check): sizeable SVGs with marks, or blocks named as one
859
+ const chartRe = /(^|[\s_-])(chart|plot|graph|timeline|calendar|cal|gantt|heatmap|matrix|strip)([\s_-]|$)/i;
860
+ const chartCount = [...B.querySelectorAll('svg')].filter((s) => { if (s.parentElement && s.parentElement.closest('svg')) return false; const r = s.getBoundingClientRect(); return r.width >= 200 && r.height >= 80 && s.querySelectorAll('path, rect, line, polyline, circle').length >= 6 && shown(s); }).length
861
+ + [...B.querySelectorAll('[class]')].filter((e) => typeof e.className === 'string' && chartRe.test(e.className) && !e.closest('svg') && (() => { const r = e.getBoundingClientRect(); return r.width >= 200 && r.height >= 80 && shown(e); })()).length;
862
+ return {
863
+ viewport: { w: W, h: H }, dark: lum(pageBg) < 0.2, ground: hex(pageBg),
864
+ sidebar, topbar, canvas, hero, charts: chartCount,
865
+ layout: { panels: tops.length, firstScreen, ragged: ragged.slice(0, 6), voids: voids.slice(0, 4) },
866
+ tiles,
867
+ panels: { count: panels.length, radii: [...radii.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6), heavyBorders, borderSamples, heavyShadows, softShadows, shadowSamples, glass },
868
+ chips: { count: chips.length, hues: chipHues.size, samples: chips.slice(0, 4) },
869
+ numbers: { cells: cells.length, leftAligned: leftNums, figures: figures.length, tabularShare, kpiMax: figures.length ? Math.max(...figures.map((f) => f.size)) : null },
870
+ rows: rows.length ? { count: rows.length, median: rows.sort((a, b) => a - b)[Math.floor(rows.length / 2)] } : null,
871
+ chartHues: hues.size,
872
+ placeholders,
873
+ type: { under13, tiny, tinySamples, sizes: [...sizes.keys()].sort((a, b) => a - b) },
874
+ title: h1 && h1r.width > 0 ? { text: (h1.innerText || '').trim().slice(0, 60), size: Math.round(parseFloat(getComputedStyle(h1).fontSize)), top: Math.round(h1r.top + SY) } : null,
875
+ };
876
+ }
877
+
878
+ // Share of a screenshot's pixels that are strongly coloured (saturated, neither near-black nor near-white).
879
+ // The studied app screens keep it near 3%; pale tints barely count. Runs inside any page; takes a data: URL.
880
+ export async function colourShare(dataUrl) {
881
+ const img = new Image(); img.src = dataUrl; await img.decode();
882
+ const w = 240, h = Math.max(1, Math.round((240 * img.height) / img.width));
883
+ const c = document.createElement('canvas'); c.width = w; c.height = h;
884
+ const x = c.getContext('2d', { willReadFrequently: true }); x.drawImage(img, 0, 0, w, h);
885
+ const d = x.getImageData(0, 0, w, h).data; let n = 0;
886
+ for (let i = 0; i < d.length; i += 4) {
887
+ const r = d[i] / 255, g = d[i + 1] / 255, b = d[i + 2] / 255, mx = Math.max(r, g, b), mn = Math.min(r, g, b), l = (mx + mn) / 2;
888
+ const s = mx === mn ? 0 : l > 0.5 ? (mx - mn) / (2 - mx - mn) : (mx - mn) / (mx + mn);
889
+ if (s >= 0.4 && l >= 0.18 && l <= 0.82) n++;
890
+ }
891
+ return Math.round((n / (w * h)) * 1000) / 1000;
892
+ }
893
+
894
+ // Dominant colours of a screenshot. Runs inside any page; takes a data: URL.
895
+ export async function palette(dataUrl) {
896
+ const img = new Image(); img.src = dataUrl; await img.decode();
897
+ const w = 96, h = Math.max(1, Math.round((96 * img.height) / img.width));
898
+ const c = document.createElement('canvas'); c.width = w; c.height = h;
899
+ const x = c.getContext('2d', { willReadFrequently: true }); x.drawImage(img, 0, 0, w, h);
900
+ const d = x.getImageData(0, 0, w, h).data; const bins = new Map();
901
+ for (let i = 0; i < d.length; i += 4) {
902
+ const k = (d[i] >> 4) << 8 | (d[i + 1] >> 4) << 4 | (d[i + 2] >> 4);
903
+ const b = bins.get(k) || [0, 0, 0, 0]; b[0] += d[i]; b[1] += d[i + 1]; b[2] += d[i + 2]; b[3]++; bins.set(k, b);
904
+ }
905
+ const total = w * h;
906
+ return [...bins.values()].sort((a, b) => b[3] - a[3]).slice(0, 6).map((b) => ({
907
+ color: '#' + [0, 1, 2].map((i) => Math.round(b[i] / b[3]).toString(16).padStart(2, '0')).join(''), share: Math.round((b[3] / total) * 100) / 100,
908
+ }));
909
+ }