expxagents 0.25.1 → 0.25.2

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 (594) hide show
  1. package/assets/agents/_catalog.yaml +35 -0
  2. package/assets/agents/accounting/accountant.agent.md +41 -0
  3. package/assets/agents/accounting/audit-analyst.agent.md +41 -0
  4. package/assets/agents/accounting/financial-reporting.agent.md +41 -0
  5. package/assets/agents/accounting/fiscal-analyst.agent.md +41 -0
  6. package/assets/agents/accounting/payroll-specialist.agent.md +41 -0
  7. package/assets/agents/accounting/tax-compliance.agent.md +41 -0
  8. package/assets/agents/administrative/document-controller.agent.md +41 -0
  9. package/assets/agents/administrative/office-manager.agent.md +41 -0
  10. package/assets/agents/administrative/process-documentation-officer.agent.md +41 -0
  11. package/assets/agents/administrative/procurement-specialist.agent.md +41 -0
  12. package/assets/agents/board/board-report-writer.agent.md +41 -0
  13. package/assets/agents/board/business-intelligence.agent.md +41 -0
  14. package/assets/agents/board/governance-officer.agent.md +41 -0
  15. package/assets/agents/board/okr-manager.agent.md +41 -0
  16. package/assets/agents/board/risk-analyst.agent.md +41 -0
  17. package/assets/agents/board/strategic-advisor.agent.md +41 -0
  18. package/assets/agents/commercial/account-executive.agent.md +41 -0
  19. package/assets/agents/commercial/crm-manager.agent.md +41 -0
  20. package/assets/agents/commercial/pricing-strategist.agent.md +41 -0
  21. package/assets/agents/commercial/proposal-writer.agent.md +41 -0
  22. package/assets/agents/commercial/sdr.agent.md +41 -0
  23. package/assets/agents/compliance/compliance-officer.agent.md +41 -0
  24. package/assets/agents/compliance/data-privacy-specialist.agent.md +41 -0
  25. package/assets/agents/compliance/internal-auditor.agent.md +41 -0
  26. package/assets/agents/compliance/regulatory-monitor.agent.md +41 -0
  27. package/assets/agents/customer-success/churn-prevention.agent.md +41 -0
  28. package/assets/agents/customer-success/csm.agent.md +41 -0
  29. package/assets/agents/customer-success/expansion-manager.agent.md +41 -0
  30. package/assets/agents/customer-success/nps-analyst.agent.md +41 -0
  31. package/assets/agents/customer-success/renewal-manager.agent.md +41 -0
  32. package/assets/agents/development/android-developer.agent.md +41 -0
  33. package/assets/agents/development/backend-developer.agent.md +42 -0
  34. package/assets/agents/development/business-analyst.agent.md +41 -0
  35. package/assets/agents/development/code-reviewer.agent.md +41 -0
  36. package/assets/agents/development/cross-platform-mobile.agent.md +41 -0
  37. package/assets/agents/development/dba.agent.md +41 -0
  38. package/assets/agents/development/desktop-developer.agent.md +41 -0
  39. package/assets/agents/development/devops-engineer.agent.md +41 -0
  40. package/assets/agents/development/frontend-developer.agent.md +103 -0
  41. package/assets/agents/development/ios-developer.agent.md +41 -0
  42. package/assets/agents/development/product-manager.agent.md +41 -0
  43. package/assets/agents/development/qa-engineer.agent.md +41 -0
  44. package/assets/agents/development/scrum-master.agent.md +41 -0
  45. package/assets/agents/development/security-analyst.agent.md +41 -0
  46. package/assets/agents/development/tech-lead.agent.md +42 -0
  47. package/assets/agents/development/tech-writer.agent.md +41 -0
  48. package/assets/agents/development/ux-design-expert.agent.md +108 -0
  49. package/assets/agents/development/ux-designer.agent.md +41 -0
  50. package/assets/agents/finance/accounts-manager.agent.md +41 -0
  51. package/assets/agents/finance/billing-analyst.agent.md +41 -0
  52. package/assets/agents/finance/budget-planner.agent.md +41 -0
  53. package/assets/agents/finance/financial-controller.agent.md +41 -0
  54. package/assets/agents/hr/benefits-manager.agent.md +41 -0
  55. package/assets/agents/hr/hr-onboarding.agent.md +41 -0
  56. package/assets/agents/hr/interview-coordinator.agent.md +41 -0
  57. package/assets/agents/hr/people-culture.agent.md +41 -0
  58. package/assets/agents/hr/performance-analyst.agent.md +41 -0
  59. package/assets/agents/hr/recruiter.agent.md +41 -0
  60. package/assets/agents/implantation/deployment-manager.agent.md +41 -0
  61. package/assets/agents/implantation/environment-specialist.agent.md +41 -0
  62. package/assets/agents/implantation/go-live-coordinator.agent.md +41 -0
  63. package/assets/agents/implantation/integration-specialist.agent.md +41 -0
  64. package/assets/agents/implantation/migration-specialist.agent.md +41 -0
  65. package/assets/agents/legal/contract-manager.agent.md +41 -0
  66. package/assets/agents/legal/ip-specialist.agent.md +41 -0
  67. package/assets/agents/legal/labor-attorney.agent.md +41 -0
  68. package/assets/agents/legal/legal-counsel.agent.md +41 -0
  69. package/assets/agents/marketing/brand-guardian.agent.md +40 -0
  70. package/assets/agents/marketing/content-creator.agent.md +41 -0
  71. package/assets/agents/marketing/email-marketing.agent.md +41 -0
  72. package/assets/agents/marketing/landing-page-builder.agent.md +138 -0
  73. package/assets/agents/marketing/marketing-analyst.agent.md +41 -0
  74. package/assets/agents/marketing/paid-ads-manager.agent.md +41 -0
  75. package/assets/agents/marketing/seo-specialist.agent.md +41 -0
  76. package/assets/agents/marketing/social-media-manager.agent.md +41 -0
  77. package/assets/agents/rnd/benchmark-analyst.agent.md +41 -0
  78. package/assets/agents/rnd/innovation-scout.agent.md +41 -0
  79. package/assets/agents/rnd/market-researcher.agent.md +41 -0
  80. package/assets/agents/rnd/product-analyst.agent.md +41 -0
  81. package/assets/agents/rnd/prototype-builder.agent.md +41 -0
  82. package/assets/agents/support/knowledge-base-manager.agent.md +41 -0
  83. package/assets/agents/support/l1-support.agent.md +41 -0
  84. package/assets/agents/support/l2-support.agent.md +41 -0
  85. package/assets/agents/support/l3-support.agent.md +41 -0
  86. package/assets/agents/support/sla-monitor.agent.md +41 -0
  87. package/assets/agents/training/assessment-creator.agent.md +41 -0
  88. package/assets/agents/training/onboarding-coach.agent.md +41 -0
  89. package/assets/agents/training/training-designer.agent.md +41 -0
  90. package/assets/agents/training/workshop-facilitator.agent.md +41 -0
  91. package/assets/core/best-practices/_catalog.yaml +91 -0
  92. package/assets/core/best-practices/api-documentation.md +137 -0
  93. package/assets/core/best-practices/blog-post.md +86 -0
  94. package/assets/core/best-practices/blog-seo.md +91 -0
  95. package/assets/core/best-practices/code-review.md +97 -0
  96. package/assets/core/best-practices/copywriting.md +75 -0
  97. package/assets/core/best-practices/data-analysis.md +93 -0
  98. package/assets/core/best-practices/deploy-checklist.md +99 -0
  99. package/assets/core/best-practices/email-newsletter.md +84 -0
  100. package/assets/core/best-practices/email-sales.md +91 -0
  101. package/assets/core/best-practices/fullstack-page-generation.md +936 -0
  102. package/assets/core/best-practices/image-design.md +78 -0
  103. package/assets/core/best-practices/instagram-feed.md +70 -0
  104. package/assets/core/best-practices/instagram-reels.md +75 -0
  105. package/assets/core/best-practices/instagram-stories.md +68 -0
  106. package/assets/core/best-practices/landing-page-react.md +2263 -0
  107. package/assets/core/best-practices/landing-page.md +279 -0
  108. package/assets/core/best-practices/linkedin-article.md +83 -0
  109. package/assets/core/best-practices/linkedin-post.md +84 -0
  110. package/assets/core/best-practices/researching.md +89 -0
  111. package/assets/core/best-practices/review.md +95 -0
  112. package/assets/core/best-practices/sprint-planning.md +91 -0
  113. package/assets/core/best-practices/strategist.md +95 -0
  114. package/assets/core/best-practices/technical-writing.md +104 -0
  115. package/assets/core/best-practices/twitter-post.md +75 -0
  116. package/assets/core/best-practices/twitter-thread.md +92 -0
  117. package/assets/core/best-practices/whatsapp-broadcast.md +95 -0
  118. package/assets/core/best-practices/youtube-script.md +80 -0
  119. package/assets/core/best-practices/youtube-shorts.md +76 -0
  120. package/assets/core/prompts/insight-hunter.prompt.md +62 -0
  121. package/assets/core/runner.pipeline.md +200 -0
  122. package/assets/core/skills.engine.md +65 -0
  123. package/assets/core/solution-architect.agent.md +329 -0
  124. package/assets/mcps/_catalog.yaml +17 -0
  125. package/assets/mcps/figma.mcp.yaml +35 -0
  126. package/assets/mcps/github.mcp.yaml +44 -0
  127. package/assets/mcps/linear.mcp.yaml +37 -0
  128. package/assets/mcps/notion.mcp.yaml +37 -0
  129. package/assets/mcps/pencil.mcp.yaml +32 -0
  130. package/assets/mcps/postgresql.mcp.yaml +39 -0
  131. package/assets/mcps/sentry.mcp.yaml +41 -0
  132. package/assets/mcps/slack.mcp.yaml +37 -0
  133. package/assets/mcps/vercel.mcp.yaml +39 -0
  134. package/assets/templates/_expxagents/_memory/company.md +25 -0
  135. package/assets/templates/_expxagents/_memory/preferences.md +6 -0
  136. package/assets/templates/squads/_memory/memories.md +16 -0
  137. package/dist/dashboard/assets/BufferResource-D79vaoFm.js +185 -0
  138. package/dist/dashboard/assets/CanvasRenderer-BUoxTNKV.js +1 -0
  139. package/dist/dashboard/assets/JarvisView-DSN7xWMz.js +1 -0
  140. package/dist/dashboard/assets/RenderTargetSystem-B7rwTXA1.js +172 -0
  141. package/dist/dashboard/assets/ThreeBackground-BQTdScX-.js +1 -0
  142. package/dist/dashboard/assets/WebGLRenderer-DgdVNsZ9.js +156 -0
  143. package/dist/dashboard/assets/WebGPURenderer-DnQNvjEQ.js +41 -0
  144. package/dist/dashboard/assets/browserAll-Cbsk7DE4.js +14 -0
  145. package/dist/dashboard/assets/index-CrlhoBta.js +783 -0
  146. package/dist/dashboard/assets/index-DtbIzZ5n.css +1 -0
  147. package/dist/dashboard/assets/three-BZk_I9Ly.js +4057 -0
  148. package/dist/dashboard/assets/webworkerAll-BLmfReEj.js +83 -0
  149. package/dist/dashboard/index.html +13 -0
  150. package/dist/server/api/__tests__/cost-routes.test.d.ts +2 -0
  151. package/dist/server/api/__tests__/cost-routes.test.d.ts.map +1 -0
  152. package/dist/server/api/__tests__/cost-routes.test.js +54 -0
  153. package/dist/server/api/__tests__/cost-routes.test.js.map +1 -0
  154. package/dist/server/api/__tests__/files-routes.test.d.ts +2 -0
  155. package/dist/server/api/__tests__/files-routes.test.d.ts.map +1 -0
  156. package/dist/server/api/__tests__/files-routes.test.js +85 -0
  157. package/dist/server/api/__tests__/files-routes.test.js.map +1 -0
  158. package/dist/server/api/__tests__/graph-routes.test.d.ts +2 -0
  159. package/dist/server/api/__tests__/graph-routes.test.d.ts.map +1 -0
  160. package/dist/server/api/__tests__/graph-routes.test.js +105 -0
  161. package/dist/server/api/__tests__/graph-routes.test.js.map +1 -0
  162. package/dist/server/api/__tests__/health-routes.test.d.ts +2 -0
  163. package/dist/server/api/__tests__/health-routes.test.d.ts.map +1 -0
  164. package/dist/server/api/__tests__/health-routes.test.js +22 -0
  165. package/dist/server/api/__tests__/health-routes.test.js.map +1 -0
  166. package/dist/server/api/__tests__/integration-routes.test.d.ts +2 -0
  167. package/dist/server/api/__tests__/integration-routes.test.d.ts.map +1 -0
  168. package/dist/server/api/__tests__/integration-routes.test.js +243 -0
  169. package/dist/server/api/__tests__/integration-routes.test.js.map +1 -0
  170. package/dist/server/api/__tests__/kanban-routes.test.d.ts +2 -0
  171. package/dist/server/api/__tests__/kanban-routes.test.d.ts.map +1 -0
  172. package/dist/server/api/__tests__/kanban-routes.test.js +316 -0
  173. package/dist/server/api/__tests__/kanban-routes.test.js.map +1 -0
  174. package/dist/server/api/__tests__/log-routes.test.d.ts +2 -0
  175. package/dist/server/api/__tests__/log-routes.test.d.ts.map +1 -0
  176. package/dist/server/api/__tests__/log-routes.test.js +35 -0
  177. package/dist/server/api/__tests__/log-routes.test.js.map +1 -0
  178. package/dist/server/api/__tests__/orgchart-routes.test.d.ts +2 -0
  179. package/dist/server/api/__tests__/orgchart-routes.test.d.ts.map +1 -0
  180. package/dist/server/api/__tests__/orgchart-routes.test.js +161 -0
  181. package/dist/server/api/__tests__/orgchart-routes.test.js.map +1 -0
  182. package/dist/server/api/__tests__/settings-routes.test.d.ts +2 -0
  183. package/dist/server/api/__tests__/settings-routes.test.d.ts.map +1 -0
  184. package/dist/server/api/__tests__/settings-routes.test.js +177 -0
  185. package/dist/server/api/__tests__/settings-routes.test.js.map +1 -0
  186. package/dist/server/api/__tests__/system-routes.test.d.ts +2 -0
  187. package/dist/server/api/__tests__/system-routes.test.d.ts.map +1 -0
  188. package/dist/server/api/__tests__/system-routes.test.js +79 -0
  189. package/dist/server/api/__tests__/system-routes.test.js.map +1 -0
  190. package/dist/server/api/__tests__/team-routes.test.d.ts +2 -0
  191. package/dist/server/api/__tests__/team-routes.test.d.ts.map +1 -0
  192. package/dist/server/api/__tests__/team-routes.test.js +116 -0
  193. package/dist/server/api/__tests__/team-routes.test.js.map +1 -0
  194. package/dist/server/api/__tests__/webhook-routes.test.d.ts +2 -0
  195. package/dist/server/api/__tests__/webhook-routes.test.d.ts.map +1 -0
  196. package/dist/server/api/__tests__/webhook-routes.test.js +111 -0
  197. package/dist/server/api/__tests__/webhook-routes.test.js.map +1 -0
  198. package/dist/server/api/activity-routes.d.ts +8 -0
  199. package/dist/server/api/activity-routes.d.ts.map +1 -0
  200. package/dist/server/api/activity-routes.js +34 -0
  201. package/dist/server/api/activity-routes.js.map +1 -0
  202. package/dist/server/api/chat-api-routes.d.ts +4 -0
  203. package/dist/server/api/chat-api-routes.d.ts.map +1 -0
  204. package/dist/server/api/chat-api-routes.js +28 -0
  205. package/dist/server/api/chat-api-routes.js.map +1 -0
  206. package/dist/server/api/cost-routes.d.ts +8 -0
  207. package/dist/server/api/cost-routes.d.ts.map +1 -0
  208. package/dist/server/api/cost-routes.js +39 -0
  209. package/dist/server/api/cost-routes.js.map +1 -0
  210. package/dist/server/api/dashboard-routes.d.ts +10 -0
  211. package/dist/server/api/dashboard-routes.d.ts.map +1 -0
  212. package/dist/server/api/dashboard-routes.js +128 -0
  213. package/dist/server/api/dashboard-routes.js.map +1 -0
  214. package/dist/server/api/files-routes.d.ts +5 -0
  215. package/dist/server/api/files-routes.d.ts.map +1 -0
  216. package/dist/server/api/files-routes.js +218 -0
  217. package/dist/server/api/files-routes.js.map +1 -0
  218. package/dist/server/api/graph-routes.d.ts +24 -0
  219. package/dist/server/api/graph-routes.d.ts.map +1 -0
  220. package/dist/server/api/graph-routes.js +208 -0
  221. package/dist/server/api/graph-routes.js.map +1 -0
  222. package/dist/server/api/health-routes.d.ts +9 -0
  223. package/dist/server/api/health-routes.d.ts.map +1 -0
  224. package/dist/server/api/health-routes.js +46 -0
  225. package/dist/server/api/health-routes.js.map +1 -0
  226. package/dist/server/api/integration-routes.d.ts +23 -0
  227. package/dist/server/api/integration-routes.d.ts.map +1 -0
  228. package/dist/server/api/integration-routes.js +326 -0
  229. package/dist/server/api/integration-routes.js.map +1 -0
  230. package/dist/server/api/kanban-routes.d.ts +8 -0
  231. package/dist/server/api/kanban-routes.d.ts.map +1 -0
  232. package/dist/server/api/kanban-routes.js +128 -0
  233. package/dist/server/api/kanban-routes.js.map +1 -0
  234. package/dist/server/api/knowledge-routes.d.ts +8 -0
  235. package/dist/server/api/knowledge-routes.d.ts.map +1 -0
  236. package/dist/server/api/knowledge-routes.js +82 -0
  237. package/dist/server/api/knowledge-routes.js.map +1 -0
  238. package/dist/server/api/log-routes.d.ts +8 -0
  239. package/dist/server/api/log-routes.d.ts.map +1 -0
  240. package/dist/server/api/log-routes.js +31 -0
  241. package/dist/server/api/log-routes.js.map +1 -0
  242. package/dist/server/api/orgchart-routes.d.ts +7 -0
  243. package/dist/server/api/orgchart-routes.d.ts.map +1 -0
  244. package/dist/server/api/orgchart-routes.js +154 -0
  245. package/dist/server/api/orgchart-routes.js.map +1 -0
  246. package/dist/server/api/permissions-routes.d.ts +8 -0
  247. package/dist/server/api/permissions-routes.d.ts.map +1 -0
  248. package/dist/server/api/permissions-routes.js +37 -0
  249. package/dist/server/api/permissions-routes.js.map +1 -0
  250. package/dist/server/api/registry-routes.d.ts +7 -0
  251. package/dist/server/api/registry-routes.d.ts.map +1 -0
  252. package/dist/server/api/registry-routes.js +51 -0
  253. package/dist/server/api/registry-routes.js.map +1 -0
  254. package/dist/server/api/settings-routes.d.ts +11 -0
  255. package/dist/server/api/settings-routes.d.ts.map +1 -0
  256. package/dist/server/api/settings-routes.js +167 -0
  257. package/dist/server/api/settings-routes.js.map +1 -0
  258. package/dist/server/api/squads-routes.d.ts +9 -0
  259. package/dist/server/api/squads-routes.d.ts.map +1 -0
  260. package/dist/server/api/squads-routes.js +171 -0
  261. package/dist/server/api/squads-routes.js.map +1 -0
  262. package/dist/server/api/system-routes.d.ts +9 -0
  263. package/dist/server/api/system-routes.d.ts.map +1 -0
  264. package/dist/server/api/system-routes.js +241 -0
  265. package/dist/server/api/system-routes.js.map +1 -0
  266. package/dist/server/api/team-routes.d.ts +8 -0
  267. package/dist/server/api/team-routes.d.ts.map +1 -0
  268. package/dist/server/api/team-routes.js +55 -0
  269. package/dist/server/api/team-routes.js.map +1 -0
  270. package/dist/server/api/users-routes.d.ts +8 -0
  271. package/dist/server/api/users-routes.d.ts.map +1 -0
  272. package/dist/server/api/users-routes.js +65 -0
  273. package/dist/server/api/users-routes.js.map +1 -0
  274. package/dist/server/api/webhook-routes.d.ts +9 -0
  275. package/dist/server/api/webhook-routes.d.ts.map +1 -0
  276. package/dist/server/api/webhook-routes.js +82 -0
  277. package/dist/server/api/webhook-routes.js.map +1 -0
  278. package/dist/server/app.d.ts +9 -0
  279. package/dist/server/app.d.ts.map +1 -0
  280. package/dist/server/app.js +265 -0
  281. package/dist/server/app.js.map +1 -0
  282. package/dist/server/auth/auth-middleware.d.ts +13 -0
  283. package/dist/server/auth/auth-middleware.d.ts.map +1 -0
  284. package/dist/server/auth/auth-middleware.js +25 -0
  285. package/dist/server/auth/auth-middleware.js.map +1 -0
  286. package/dist/server/auth/auth-routes.d.ts +9 -0
  287. package/dist/server/auth/auth-routes.d.ts.map +1 -0
  288. package/dist/server/auth/auth-routes.js +152 -0
  289. package/dist/server/auth/auth-routes.js.map +1 -0
  290. package/dist/server/auth/jwt.d.ts +14 -0
  291. package/dist/server/auth/jwt.d.ts.map +1 -0
  292. package/dist/server/auth/jwt.js +16 -0
  293. package/dist/server/auth/jwt.js.map +1 -0
  294. package/dist/server/auth/password.d.ts +3 -0
  295. package/dist/server/auth/password.d.ts.map +1 -0
  296. package/dist/server/auth/password.js +9 -0
  297. package/dist/server/auth/password.js.map +1 -0
  298. package/dist/server/bridge/__tests__/chat-handler.test.d.ts +2 -0
  299. package/dist/server/bridge/__tests__/chat-handler.test.d.ts.map +1 -0
  300. package/dist/server/bridge/__tests__/chat-handler.test.js +143 -0
  301. package/dist/server/bridge/__tests__/chat-handler.test.js.map +1 -0
  302. package/dist/server/bridge/__tests__/chat-integration.test.d.ts +2 -0
  303. package/dist/server/bridge/__tests__/chat-integration.test.d.ts.map +1 -0
  304. package/dist/server/bridge/__tests__/chat-integration.test.js +129 -0
  305. package/dist/server/bridge/__tests__/chat-integration.test.js.map +1 -0
  306. package/dist/server/bridge/__tests__/claude-bridge.test.d.ts +2 -0
  307. package/dist/server/bridge/__tests__/claude-bridge.test.d.ts.map +1 -0
  308. package/dist/server/bridge/__tests__/claude-bridge.test.js +300 -0
  309. package/dist/server/bridge/__tests__/claude-bridge.test.js.map +1 -0
  310. package/dist/server/bridge/__tests__/conversation.test.d.ts +2 -0
  311. package/dist/server/bridge/__tests__/conversation.test.d.ts.map +1 -0
  312. package/dist/server/bridge/__tests__/conversation.test.js +168 -0
  313. package/dist/server/bridge/__tests__/conversation.test.js.map +1 -0
  314. package/dist/server/bridge/__tests__/registry.test.d.ts +2 -0
  315. package/dist/server/bridge/__tests__/registry.test.d.ts.map +1 -0
  316. package/dist/server/bridge/__tests__/registry.test.js +45 -0
  317. package/dist/server/bridge/__tests__/registry.test.js.map +1 -0
  318. package/dist/server/bridge/__tests__/stream-parser.test.d.ts +2 -0
  319. package/dist/server/bridge/__tests__/stream-parser.test.d.ts.map +1 -0
  320. package/dist/server/bridge/__tests__/stream-parser.test.js +66 -0
  321. package/dist/server/bridge/__tests__/stream-parser.test.js.map +1 -0
  322. package/dist/server/bridge/chat-handler.d.ts +32 -0
  323. package/dist/server/bridge/chat-handler.d.ts.map +1 -0
  324. package/dist/server/bridge/chat-handler.js +356 -0
  325. package/dist/server/bridge/chat-handler.js.map +1 -0
  326. package/dist/server/bridge/claude-bridge.d.ts +21 -0
  327. package/dist/server/bridge/claude-bridge.d.ts.map +1 -0
  328. package/dist/server/bridge/claude-bridge.js +273 -0
  329. package/dist/server/bridge/claude-bridge.js.map +1 -0
  330. package/dist/server/bridge/conversation.d.ts +45 -0
  331. package/dist/server/bridge/conversation.d.ts.map +1 -0
  332. package/dist/server/bridge/conversation.js +78 -0
  333. package/dist/server/bridge/conversation.js.map +1 -0
  334. package/dist/server/bridge/engine-manager.d.ts +13 -0
  335. package/dist/server/bridge/engine-manager.d.ts.map +1 -0
  336. package/dist/server/bridge/engine-manager.js +54 -0
  337. package/dist/server/bridge/engine-manager.js.map +1 -0
  338. package/dist/server/bridge/engine.d.ts +37 -0
  339. package/dist/server/bridge/engine.d.ts.map +1 -0
  340. package/dist/server/bridge/engine.js +2 -0
  341. package/dist/server/bridge/engine.js.map +1 -0
  342. package/dist/server/bridge/factory.d.ts +4 -0
  343. package/dist/server/bridge/factory.d.ts.map +1 -0
  344. package/dist/server/bridge/factory.js +18 -0
  345. package/dist/server/bridge/factory.js.map +1 -0
  346. package/dist/server/bridge/opencode-bridge.d.ts +27 -0
  347. package/dist/server/bridge/opencode-bridge.d.ts.map +1 -0
  348. package/dist/server/bridge/opencode-bridge.js +163 -0
  349. package/dist/server/bridge/opencode-bridge.js.map +1 -0
  350. package/dist/server/bridge/registry.d.ts +19 -0
  351. package/dist/server/bridge/registry.d.ts.map +1 -0
  352. package/dist/server/bridge/registry.js +29 -0
  353. package/dist/server/bridge/registry.js.map +1 -0
  354. package/dist/server/bridge/session-journal.d.ts +25 -0
  355. package/dist/server/bridge/session-journal.d.ts.map +1 -0
  356. package/dist/server/bridge/session-journal.js +69 -0
  357. package/dist/server/bridge/session-journal.js.map +1 -0
  358. package/dist/server/bridge/stream-parser.d.ts +14 -0
  359. package/dist/server/bridge/stream-parser.d.ts.map +1 -0
  360. package/dist/server/bridge/stream-parser.js +26 -0
  361. package/dist/server/bridge/stream-parser.js.map +1 -0
  362. package/dist/server/config/engine-config.d.ts +4 -0
  363. package/dist/server/config/engine-config.d.ts.map +1 -0
  364. package/dist/server/config/engine-config.js +24 -0
  365. package/dist/server/config/engine-config.js.map +1 -0
  366. package/dist/server/config.d.ts +17 -0
  367. package/dist/server/config.d.ts.map +1 -0
  368. package/dist/server/config.js +39 -0
  369. package/dist/server/config.js.map +1 -0
  370. package/dist/server/db/__tests__/chat-tables.test.d.ts +2 -0
  371. package/dist/server/db/__tests__/chat-tables.test.d.ts.map +1 -0
  372. package/dist/server/db/__tests__/chat-tables.test.js +82 -0
  373. package/dist/server/db/__tests__/chat-tables.test.js.map +1 -0
  374. package/dist/server/db/__tests__/email-schema.test.d.ts +2 -0
  375. package/dist/server/db/__tests__/email-schema.test.d.ts.map +1 -0
  376. package/dist/server/db/__tests__/email-schema.test.js +53 -0
  377. package/dist/server/db/__tests__/email-schema.test.js.map +1 -0
  378. package/dist/server/db/__tests__/scheduler-schema.test.d.ts +2 -0
  379. package/dist/server/db/__tests__/scheduler-schema.test.d.ts.map +1 -0
  380. package/dist/server/db/__tests__/scheduler-schema.test.js +52 -0
  381. package/dist/server/db/__tests__/scheduler-schema.test.js.map +1 -0
  382. package/dist/server/db/connection.d.ts +4 -0
  383. package/dist/server/db/connection.d.ts.map +1 -0
  384. package/dist/server/db/connection.js +40 -0
  385. package/dist/server/db/connection.js.map +1 -0
  386. package/dist/server/db/migrations.d.ts +4 -0
  387. package/dist/server/db/migrations.d.ts.map +1 -0
  388. package/dist/server/db/migrations.js +47 -0
  389. package/dist/server/db/migrations.js.map +1 -0
  390. package/dist/server/db/schema.d.ts +2 -0
  391. package/dist/server/db/schema.d.ts.map +1 -0
  392. package/dist/server/db/schema.js +288 -0
  393. package/dist/server/db/schema.js.map +1 -0
  394. package/dist/server/email/__tests__/campaign-routes.test.d.ts +2 -0
  395. package/dist/server/email/__tests__/campaign-routes.test.d.ts.map +1 -0
  396. package/dist/server/email/__tests__/campaign-routes.test.js +216 -0
  397. package/dist/server/email/__tests__/campaign-routes.test.js.map +1 -0
  398. package/dist/server/email/__tests__/campaign-service.test.d.ts +2 -0
  399. package/dist/server/email/__tests__/campaign-service.test.d.ts.map +1 -0
  400. package/dist/server/email/__tests__/campaign-service.test.js +79 -0
  401. package/dist/server/email/__tests__/campaign-service.test.js.map +1 -0
  402. package/dist/server/email/__tests__/email-queue-worker.test.d.ts +2 -0
  403. package/dist/server/email/__tests__/email-queue-worker.test.d.ts.map +1 -0
  404. package/dist/server/email/__tests__/email-queue-worker.test.js +93 -0
  405. package/dist/server/email/__tests__/email-queue-worker.test.js.map +1 -0
  406. package/dist/server/email/__tests__/email-utils.test.d.ts +2 -0
  407. package/dist/server/email/__tests__/email-utils.test.d.ts.map +1 -0
  408. package/dist/server/email/__tests__/email-utils.test.js +36 -0
  409. package/dist/server/email/__tests__/email-utils.test.js.map +1 -0
  410. package/dist/server/email/__tests__/lead-routes.test.d.ts +2 -0
  411. package/dist/server/email/__tests__/lead-routes.test.d.ts.map +1 -0
  412. package/dist/server/email/__tests__/lead-routes.test.js +180 -0
  413. package/dist/server/email/__tests__/lead-routes.test.js.map +1 -0
  414. package/dist/server/email/__tests__/lead-service.test.d.ts +2 -0
  415. package/dist/server/email/__tests__/lead-service.test.d.ts.map +1 -0
  416. package/dist/server/email/__tests__/lead-service.test.js +113 -0
  417. package/dist/server/email/__tests__/lead-service.test.js.map +1 -0
  418. package/dist/server/email/__tests__/ses-client.test.d.ts +2 -0
  419. package/dist/server/email/__tests__/ses-client.test.d.ts.map +1 -0
  420. package/dist/server/email/__tests__/ses-client.test.js +48 -0
  421. package/dist/server/email/__tests__/ses-client.test.js.map +1 -0
  422. package/dist/server/email/__tests__/sns-webhook.test.d.ts +2 -0
  423. package/dist/server/email/__tests__/sns-webhook.test.d.ts.map +1 -0
  424. package/dist/server/email/__tests__/sns-webhook.test.js +40 -0
  425. package/dist/server/email/__tests__/sns-webhook.test.js.map +1 -0
  426. package/dist/server/email/campaign-routes.d.ts +8 -0
  427. package/dist/server/email/campaign-routes.d.ts.map +1 -0
  428. package/dist/server/email/campaign-routes.js +65 -0
  429. package/dist/server/email/campaign-routes.js.map +1 -0
  430. package/dist/server/email/campaign-service.d.ts +55 -0
  431. package/dist/server/email/campaign-service.d.ts.map +1 -0
  432. package/dist/server/email/campaign-service.js +89 -0
  433. package/dist/server/email/campaign-service.js.map +1 -0
  434. package/dist/server/email/email-queue-worker.d.ts +27 -0
  435. package/dist/server/email/email-queue-worker.d.ts.map +1 -0
  436. package/dist/server/email/email-queue-worker.js +119 -0
  437. package/dist/server/email/email-queue-worker.js.map +1 -0
  438. package/dist/server/email/email-utils.d.ts +5 -0
  439. package/dist/server/email/email-utils.d.ts.map +1 -0
  440. package/dist/server/email/email-utils.js +24 -0
  441. package/dist/server/email/email-utils.js.map +1 -0
  442. package/dist/server/email/lead-routes.d.ts +8 -0
  443. package/dist/server/email/lead-routes.d.ts.map +1 -0
  444. package/dist/server/email/lead-routes.js +56 -0
  445. package/dist/server/email/lead-routes.js.map +1 -0
  446. package/dist/server/email/lead-service.d.ts +66 -0
  447. package/dist/server/email/lead-service.d.ts.map +1 -0
  448. package/dist/server/email/lead-service.js +138 -0
  449. package/dist/server/email/lead-service.js.map +1 -0
  450. package/dist/server/email/ses-client.d.ts +27 -0
  451. package/dist/server/email/ses-client.d.ts.map +1 -0
  452. package/dist/server/email/ses-client.js +44 -0
  453. package/dist/server/email/ses-client.js.map +1 -0
  454. package/dist/server/email/sns-webhook.d.ts +10 -0
  455. package/dist/server/email/sns-webhook.d.ts.map +1 -0
  456. package/dist/server/email/sns-webhook.js +73 -0
  457. package/dist/server/email/sns-webhook.js.map +1 -0
  458. package/dist/server/index.d.ts +2 -0
  459. package/dist/server/index.d.ts.map +1 -0
  460. package/dist/server/index.js +36 -0
  461. package/dist/server/index.js.map +1 -0
  462. package/dist/server/mcp/expxagents-mcp.d.ts +2 -0
  463. package/dist/server/mcp/expxagents-mcp.d.ts.map +1 -0
  464. package/dist/server/mcp/expxagents-mcp.js +230 -0
  465. package/dist/server/mcp/expxagents-mcp.js.map +1 -0
  466. package/dist/server/routes/__tests__/conversations.test.d.ts +2 -0
  467. package/dist/server/routes/__tests__/conversations.test.d.ts.map +1 -0
  468. package/dist/server/routes/__tests__/conversations.test.js +111 -0
  469. package/dist/server/routes/__tests__/conversations.test.js.map +1 -0
  470. package/dist/server/routes/conversations.d.ts +8 -0
  471. package/dist/server/routes/conversations.d.ts.map +1 -0
  472. package/dist/server/routes/conversations.js +36 -0
  473. package/dist/server/routes/conversations.js.map +1 -0
  474. package/dist/server/scheduler/__tests__/job-runner.test.d.ts +2 -0
  475. package/dist/server/scheduler/__tests__/job-runner.test.d.ts.map +1 -0
  476. package/dist/server/scheduler/__tests__/job-runner.test.js +488 -0
  477. package/dist/server/scheduler/__tests__/job-runner.test.js.map +1 -0
  478. package/dist/server/scheduler/__tests__/scheduler-routes.test.d.ts +2 -0
  479. package/dist/server/scheduler/__tests__/scheduler-routes.test.d.ts.map +1 -0
  480. package/dist/server/scheduler/__tests__/scheduler-routes.test.js +167 -0
  481. package/dist/server/scheduler/__tests__/scheduler-routes.test.js.map +1 -0
  482. package/dist/server/scheduler/__tests__/scheduler-service.test.d.ts +2 -0
  483. package/dist/server/scheduler/__tests__/scheduler-service.test.d.ts.map +1 -0
  484. package/dist/server/scheduler/__tests__/scheduler-service.test.js +207 -0
  485. package/dist/server/scheduler/__tests__/scheduler-service.test.js.map +1 -0
  486. package/dist/server/scheduler/job-runner.d.ts +73 -0
  487. package/dist/server/scheduler/job-runner.d.ts.map +1 -0
  488. package/dist/server/scheduler/job-runner.js +407 -0
  489. package/dist/server/scheduler/job-runner.js.map +1 -0
  490. package/dist/server/scheduler/scheduler-routes.d.ts +10 -0
  491. package/dist/server/scheduler/scheduler-routes.d.ts.map +1 -0
  492. package/dist/server/scheduler/scheduler-routes.js +105 -0
  493. package/dist/server/scheduler/scheduler-routes.js.map +1 -0
  494. package/dist/server/scheduler/scheduler-service.d.ts +55 -0
  495. package/dist/server/scheduler/scheduler-service.d.ts.map +1 -0
  496. package/dist/server/scheduler/scheduler-service.js +271 -0
  497. package/dist/server/scheduler/scheduler-service.js.map +1 -0
  498. package/dist/server/services/__tests__/cost-service.test.d.ts +2 -0
  499. package/dist/server/services/__tests__/cost-service.test.d.ts.map +1 -0
  500. package/dist/server/services/__tests__/cost-service.test.js +72 -0
  501. package/dist/server/services/__tests__/cost-service.test.js.map +1 -0
  502. package/dist/server/services/__tests__/execution-log-service.test.d.ts +2 -0
  503. package/dist/server/services/__tests__/execution-log-service.test.d.ts.map +1 -0
  504. package/dist/server/services/__tests__/execution-log-service.test.js +40 -0
  505. package/dist/server/services/__tests__/execution-log-service.test.js.map +1 -0
  506. package/dist/server/services/__tests__/integration-registry.test.d.ts +2 -0
  507. package/dist/server/services/__tests__/integration-registry.test.d.ts.map +1 -0
  508. package/dist/server/services/__tests__/integration-registry.test.js +28 -0
  509. package/dist/server/services/__tests__/integration-registry.test.js.map +1 -0
  510. package/dist/server/services/__tests__/log-service.test.d.ts +2 -0
  511. package/dist/server/services/__tests__/log-service.test.d.ts.map +1 -0
  512. package/dist/server/services/__tests__/log-service.test.js +49 -0
  513. package/dist/server/services/__tests__/log-service.test.js.map +1 -0
  514. package/dist/server/services/__tests__/permissions-service.test.d.ts +2 -0
  515. package/dist/server/services/__tests__/permissions-service.test.d.ts.map +1 -0
  516. package/dist/server/services/__tests__/permissions-service.test.js +46 -0
  517. package/dist/server/services/__tests__/permissions-service.test.js.map +1 -0
  518. package/dist/server/services/__tests__/pipeline-task-service.test.d.ts +2 -0
  519. package/dist/server/services/__tests__/pipeline-task-service.test.d.ts.map +1 -0
  520. package/dist/server/services/__tests__/pipeline-task-service.test.js +268 -0
  521. package/dist/server/services/__tests__/pipeline-task-service.test.js.map +1 -0
  522. package/dist/server/services/__tests__/state-service.test.d.ts +2 -0
  523. package/dist/server/services/__tests__/state-service.test.d.ts.map +1 -0
  524. package/dist/server/services/__tests__/state-service.test.js +136 -0
  525. package/dist/server/services/__tests__/state-service.test.js.map +1 -0
  526. package/dist/server/services/activity-service.d.ts +35 -0
  527. package/dist/server/services/activity-service.d.ts.map +1 -0
  528. package/dist/server/services/activity-service.js +92 -0
  529. package/dist/server/services/activity-service.js.map +1 -0
  530. package/dist/server/services/cost-service.d.ts +55 -0
  531. package/dist/server/services/cost-service.d.ts.map +1 -0
  532. package/dist/server/services/cost-service.js +59 -0
  533. package/dist/server/services/cost-service.js.map +1 -0
  534. package/dist/server/services/execution-log-service.d.ts +20 -0
  535. package/dist/server/services/execution-log-service.d.ts.map +1 -0
  536. package/dist/server/services/execution-log-service.js +17 -0
  537. package/dist/server/services/execution-log-service.js.map +1 -0
  538. package/dist/server/services/integration-registry.d.ts +9 -0
  539. package/dist/server/services/integration-registry.d.ts.map +1 -0
  540. package/dist/server/services/integration-registry.js +14 -0
  541. package/dist/server/services/integration-registry.js.map +1 -0
  542. package/dist/server/services/log-service.d.ts +28 -0
  543. package/dist/server/services/log-service.d.ts.map +1 -0
  544. package/dist/server/services/log-service.js +34 -0
  545. package/dist/server/services/log-service.js.map +1 -0
  546. package/dist/server/services/permissions-service.d.ts +10 -0
  547. package/dist/server/services/permissions-service.d.ts.map +1 -0
  548. package/dist/server/services/permissions-service.js +48 -0
  549. package/dist/server/services/permissions-service.js.map +1 -0
  550. package/dist/server/services/pipeline-task-service.d.ts +79 -0
  551. package/dist/server/services/pipeline-task-service.d.ts.map +1 -0
  552. package/dist/server/services/pipeline-task-service.js +122 -0
  553. package/dist/server/services/pipeline-task-service.js.map +1 -0
  554. package/dist/server/services/squad-registry.d.ts +35 -0
  555. package/dist/server/services/squad-registry.d.ts.map +1 -0
  556. package/dist/server/services/squad-registry.js +141 -0
  557. package/dist/server/services/squad-registry.js.map +1 -0
  558. package/dist/server/services/state-service.d.ts +22 -0
  559. package/dist/server/services/state-service.d.ts.map +1 -0
  560. package/dist/server/services/state-service.js +114 -0
  561. package/dist/server/services/state-service.js.map +1 -0
  562. package/dist/server/types/a2ui.d.ts +27 -0
  563. package/dist/server/types/a2ui.d.ts.map +1 -0
  564. package/dist/server/types/a2ui.js +2 -0
  565. package/dist/server/types/a2ui.js.map +1 -0
  566. package/dist/server/utils/find-free-port.d.ts +2 -0
  567. package/dist/server/utils/find-free-port.d.ts.map +1 -0
  568. package/dist/server/utils/find-free-port.js +17 -0
  569. package/dist/server/utils/find-free-port.js.map +1 -0
  570. package/dist/server/watcher/__tests__/file-watcher.test.d.ts +2 -0
  571. package/dist/server/watcher/__tests__/file-watcher.test.d.ts.map +1 -0
  572. package/dist/server/watcher/__tests__/file-watcher.test.js +81 -0
  573. package/dist/server/watcher/__tests__/file-watcher.test.js.map +1 -0
  574. package/dist/server/watcher/file-watcher.d.ts +19 -0
  575. package/dist/server/watcher/file-watcher.d.ts.map +1 -0
  576. package/dist/server/watcher/file-watcher.js +105 -0
  577. package/dist/server/watcher/file-watcher.js.map +1 -0
  578. package/dist/server/watcher/state-parser.d.ts +77 -0
  579. package/dist/server/watcher/state-parser.d.ts.map +1 -0
  580. package/dist/server/watcher/state-parser.js +78 -0
  581. package/dist/server/watcher/state-parser.js.map +1 -0
  582. package/dist/server/ws/ws-auth.d.ts +4 -0
  583. package/dist/server/ws/ws-auth.d.ts.map +1 -0
  584. package/dist/server/ws/ws-auth.js +42 -0
  585. package/dist/server/ws/ws-auth.js.map +1 -0
  586. package/dist/server/ws/ws-handler.d.ts +12 -0
  587. package/dist/server/ws/ws-handler.d.ts.map +1 -0
  588. package/dist/server/ws/ws-handler.js +171 -0
  589. package/dist/server/ws/ws-handler.js.map +1 -0
  590. package/dist/server/ws/ws-rooms.d.ts +12 -0
  591. package/dist/server/ws/ws-rooms.d.ts.map +1 -0
  592. package/dist/server/ws/ws-rooms.js +52 -0
  593. package/dist/server/ws/ws-rooms.js.map +1 -0
  594. package/package.json +2 -2
@@ -0,0 +1,2263 @@
1
+ # Landing Page React — Best Practices
2
+
3
+ ## Project Structure
4
+
5
+ ### Next.js (recommended for SEO-critical pages)
6
+ ```
7
+ ├── app/
8
+ │ ├── layout.tsx # Root layout, fonts, metadata
9
+ │ ├── page.tsx # Landing page entry
10
+ │ └── globals.css # Design tokens + base styles
11
+ ├── components/
12
+ │ ├── atoms/ # Button, Badge, Heading, Text, Input, Image
13
+ │ ├── molecules/ # FeatureCard, TestimonialCard, PricingCard, StatCounter, CTAGroup
14
+ │ ├── organisms/ # HeroSection, FeaturesGrid, PricingSection, FAQAccordion, Footer
15
+ │ └── ui/ # Container, Section (layout primitives)
16
+ ├── hooks/ # useIntersectionObserver, useMediaQuery, useCountUp
17
+ ├── lib/ # cn() utility, constants
18
+ └── public/ # Static assets (images, fonts)
19
+ ```
20
+
21
+ ### Vite + React (lightweight, no SSR needed)
22
+ ```
23
+ ├── src/
24
+ │ ├── main.tsx
25
+ │ ├── App.tsx
26
+ │ ├── index.css # Design tokens + base styles
27
+ │ ├── components/
28
+ │ │ ├── atoms/
29
+ │ │ ├── molecules/
30
+ │ │ ├── organisms/
31
+ │ │ └── ui/
32
+ │ ├── hooks/
33
+ │ └── lib/
34
+ └── public/
35
+ ```
36
+
37
+ ### File Naming Conventions
38
+ - Components: `PascalCase.tsx` — `Button.tsx`, `HeroSection.tsx`
39
+ - Hooks: `camelCase.ts` — `useIntersectionObserver.ts`
40
+ - Utilities: `camelCase.ts` — `cn.ts`
41
+ - Styles (CSS Modules): `ComponentName.module.css`
42
+ - One component per file, export as default
43
+ - Co-locate tests: `Button.test.tsx` next to `Button.tsx`
44
+
45
+ ---
46
+
47
+ ## Design System Setup
48
+
49
+ Use the SAME design tokens from the HTML version, injected via CSS custom properties. This ensures visual parity between HTML and React landing pages.
50
+
51
+ ### Design Tokens (globals.css / index.css)
52
+ ```css
53
+ :root {
54
+ /* === CORE TOKENS (identical to HTML version) === */
55
+
56
+ /* Colors — off-black/off-white, never pure #000/#fff */
57
+ --color-base-900: #0f172a;
58
+ --color-base-800: #1e293b;
59
+ --color-base-700: #334155;
60
+ --color-base-100: #f1f5f9;
61
+ --color-base-50: #f8fafc;
62
+ --color-white: #ffffff;
63
+
64
+ /* Accent — single brand color + hover state */
65
+ --color-accent: oklch(0.65 0.25 15);
66
+ --color-accent-hover: oklch(0.58 0.25 15);
67
+ --color-accent-subtle: oklch(0.65 0.25 15 / 0.1);
68
+
69
+ /* Semantic */
70
+ --color-surface: var(--color-white);
71
+ --color-surface-alt: var(--color-base-50);
72
+ --color-surface-dark: var(--color-base-900);
73
+ --color-on-surface: var(--color-base-800);
74
+ --color-on-surface-muted: var(--color-base-700);
75
+ --color-on-dark: var(--color-base-50);
76
+ --color-success: oklch(0.72 0.19 150);
77
+
78
+ /* === SPACING (4px base unit) === */
79
+ --space-1: 0.25rem;
80
+ --space-2: 0.5rem;
81
+ --space-3: 0.75rem;
82
+ --space-4: 1rem;
83
+ --space-6: 1.5rem;
84
+ --space-8: 2rem;
85
+ --space-12: 3rem;
86
+ --space-16: 4rem;
87
+ --space-20: 5rem;
88
+ --space-24: 6rem;
89
+ --space-32: 8rem;
90
+
91
+ /* Section spacing */
92
+ --section-py: clamp(var(--space-16), 10vw, var(--space-32));
93
+ --section-px: var(--space-6);
94
+ --container-max: 1200px;
95
+ --container-narrow: 720px;
96
+
97
+ /* === TYPOGRAPHY === */
98
+ --font-display: 'Plus Jakarta Sans', 'Inter', system-ui, sans-serif;
99
+ --font-body: 'Inter', system-ui, sans-serif;
100
+ --font-mono: 'JetBrains Mono', 'Fira Code', monospace;
101
+
102
+ /* Fluid type scale — min @ 320px, max @ 1200px */
103
+ --text-display: clamp(2.5rem, 5vw + 1rem, 4.5rem);
104
+ --text-h1: clamp(2rem, 4vw + 0.5rem, 3.5rem);
105
+ --text-h2: clamp(1.5rem, 3vw + 0.25rem, 2.25rem);
106
+ --text-h3: clamp(1.125rem, 2vw + 0.25rem, 1.5rem);
107
+ --text-body: clamp(1rem, 1vw + 0.5rem, 1.125rem);
108
+ --text-small: 0.875rem;
109
+ --text-caption: 0.75rem;
110
+
111
+ --leading-tight: 1.15;
112
+ --leading-normal: 1.6;
113
+ --leading-relaxed: 1.8;
114
+
115
+ --tracking-tight: -0.02em;
116
+ --tracking-normal: 0;
117
+ --tracking-wide: 0.05em;
118
+ --tracking-widest: 0.1em;
119
+
120
+ /* === RADIUS === */
121
+ --radius-sm: 0.375rem;
122
+ --radius-md: 0.75rem;
123
+ --radius-lg: 1rem;
124
+ --radius-xl: 1.5rem;
125
+ --radius-full: 9999px;
126
+
127
+ /* === SHADOWS (multi-layered) === */
128
+ --shadow-sm:
129
+ 0 1px 2px oklch(0 0 0 / 0.04),
130
+ 0 1px 3px oklch(0 0 0 / 0.06);
131
+ --shadow-md:
132
+ 0 2px 4px oklch(0 0 0 / 0.04),
133
+ 0 4px 8px oklch(0 0 0 / 0.06),
134
+ 0 8px 16px oklch(0 0 0 / 0.04);
135
+ --shadow-lg:
136
+ 0 4px 8px oklch(0 0 0 / 0.03),
137
+ 0 8px 16px oklch(0 0 0 / 0.06),
138
+ 0 16px 32px oklch(0 0 0 / 0.06),
139
+ 0 32px 64px oklch(0 0 0 / 0.04);
140
+ --shadow-xl:
141
+ 0 8px 16px oklch(0 0 0 / 0.04),
142
+ 0 16px 32px oklch(0 0 0 / 0.08),
143
+ 0 32px 64px oklch(0 0 0 / 0.08),
144
+ 0 64px 128px oklch(0 0 0 / 0.06);
145
+ --shadow-accent:
146
+ 0 4px 12px oklch(0.65 0.25 15 / 0.25),
147
+ 0 8px 24px oklch(0.65 0.25 15 / 0.15);
148
+
149
+ /* === TRANSITIONS === */
150
+ --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
151
+ --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
152
+ --duration-fast: 150ms;
153
+ --duration-normal: 250ms;
154
+ --duration-slow: 400ms;
155
+ }
156
+ ```
157
+
158
+ ### Tailwind CSS Configuration (optional)
159
+
160
+ If using Tailwind, extend the config to reference the same CSS tokens:
161
+
162
+ ```ts
163
+ // tailwind.config.ts
164
+ import type { Config } from 'tailwindcss';
165
+
166
+ const config: Config = {
167
+ content: ['./src/**/*.{ts,tsx}', './app/**/*.{ts,tsx}'],
168
+ theme: {
169
+ extend: {
170
+ colors: {
171
+ base: {
172
+ 50: 'var(--color-base-50)',
173
+ 100: 'var(--color-base-100)',
174
+ 700: 'var(--color-base-700)',
175
+ 800: 'var(--color-base-800)',
176
+ 900: 'var(--color-base-900)',
177
+ },
178
+ accent: {
179
+ DEFAULT: 'var(--color-accent)',
180
+ hover: 'var(--color-accent-hover)',
181
+ subtle: 'var(--color-accent-subtle)',
182
+ },
183
+ surface: {
184
+ DEFAULT: 'var(--color-surface)',
185
+ alt: 'var(--color-surface-alt)',
186
+ dark: 'var(--color-surface-dark)',
187
+ },
188
+ },
189
+ fontFamily: {
190
+ display: ['var(--font-display)'],
191
+ body: ['var(--font-body)'],
192
+ mono: ['var(--font-mono)'],
193
+ },
194
+ borderRadius: {
195
+ sm: 'var(--radius-sm)',
196
+ md: 'var(--radius-md)',
197
+ lg: 'var(--radius-lg)',
198
+ xl: 'var(--radius-xl)',
199
+ full: 'var(--radius-full)',
200
+ },
201
+ boxShadow: {
202
+ sm: 'var(--shadow-sm)',
203
+ md: 'var(--shadow-md)',
204
+ lg: 'var(--shadow-lg)',
205
+ xl: 'var(--shadow-xl)',
206
+ accent: 'var(--shadow-accent)',
207
+ },
208
+ },
209
+ },
210
+ plugins: [],
211
+ };
212
+
213
+ export default config;
214
+ ```
215
+
216
+ ### CSS Modules Approach (alternative to Tailwind)
217
+
218
+ ```css
219
+ /* Button.module.css */
220
+ .primary {
221
+ background: var(--color-accent);
222
+ color: var(--color-white);
223
+ padding: var(--space-4) var(--space-8);
224
+ border-radius: var(--radius-md);
225
+ font-weight: 700;
226
+ font-size: var(--text-body);
227
+ box-shadow: var(--shadow-accent);
228
+ transition: all var(--duration-normal) var(--ease-out);
229
+ border: none;
230
+ cursor: pointer;
231
+ }
232
+ .primary:hover {
233
+ background: var(--color-accent-hover);
234
+ transform: translateY(-2px);
235
+ box-shadow: var(--shadow-lg);
236
+ }
237
+ ```
238
+
239
+ ### Utility: Class Name Merging
240
+ ```ts
241
+ // lib/cn.ts
242
+ export function cn(...classes: (string | false | null | undefined)[]): string {
243
+ return classes.filter(Boolean).join(' ');
244
+ }
245
+ ```
246
+
247
+ ---
248
+
249
+ ## Component Patterns
250
+
251
+ ### Atoms
252
+
253
+ #### Button
254
+ ```tsx
255
+ // components/atoms/Button.tsx
256
+ import { type ButtonHTMLAttributes, forwardRef } from 'react';
257
+ import styles from './Button.module.css';
258
+ import { cn } from '@/lib/cn';
259
+
260
+ type ButtonVariant = 'primary' | 'secondary' | 'ghost';
261
+ type ButtonSize = 'sm' | 'md' | 'lg';
262
+
263
+ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
264
+ variant?: ButtonVariant;
265
+ size?: ButtonSize;
266
+ }
267
+
268
+ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
269
+ ({ variant = 'primary', size = 'md', className, children, ...props }, ref) => {
270
+ return (
271
+ <button
272
+ ref={ref}
273
+ className={cn(styles.base, styles[variant], styles[size], className)}
274
+ {...props}
275
+ >
276
+ {children}
277
+ </button>
278
+ );
279
+ }
280
+ );
281
+
282
+ Button.displayName = 'Button';
283
+ export default Button;
284
+ ```
285
+
286
+ ```css
287
+ /* components/atoms/Button.module.css */
288
+ .base {
289
+ display: inline-flex;
290
+ align-items: center;
291
+ justify-content: center;
292
+ gap: var(--space-2);
293
+ font-family: var(--font-body);
294
+ font-weight: 700;
295
+ border: none;
296
+ cursor: pointer;
297
+ transition: all var(--duration-normal) var(--ease-out);
298
+ text-decoration: none;
299
+ line-height: 1;
300
+ }
301
+
302
+ .primary {
303
+ background: var(--color-accent);
304
+ color: var(--color-white);
305
+ border-radius: var(--radius-md);
306
+ box-shadow: var(--shadow-accent);
307
+ }
308
+ .primary:hover {
309
+ background: var(--color-accent-hover);
310
+ transform: translateY(-2px);
311
+ box-shadow: var(--shadow-lg);
312
+ }
313
+
314
+ .secondary {
315
+ background: transparent;
316
+ color: var(--color-on-surface);
317
+ border: 2px solid var(--color-base-100);
318
+ border-radius: var(--radius-md);
319
+ }
320
+ .secondary:hover {
321
+ border-color: var(--color-accent);
322
+ color: var(--color-accent);
323
+ transform: translateY(-2px);
324
+ }
325
+
326
+ .ghost {
327
+ background: transparent;
328
+ color: var(--color-accent);
329
+ border-radius: var(--radius-md);
330
+ }
331
+ .ghost:hover {
332
+ background: var(--color-accent-subtle);
333
+ }
334
+
335
+ .sm { padding: var(--space-2) var(--space-4); font-size: var(--text-small); }
336
+ .md { padding: var(--space-4) var(--space-8); font-size: var(--text-body); }
337
+ .lg { padding: var(--space-4) var(--space-12); font-size: var(--text-body); }
338
+ ```
339
+
340
+ #### Badge
341
+ ```tsx
342
+ // components/atoms/Badge.tsx
343
+ import styles from './Badge.module.css';
344
+ import { cn } from '@/lib/cn';
345
+
346
+ interface BadgeProps {
347
+ children: React.ReactNode;
348
+ className?: string;
349
+ }
350
+
351
+ export default function Badge({ children, className }: BadgeProps) {
352
+ return (
353
+ <span className={cn(styles.badge, className)}>
354
+ {children}
355
+ </span>
356
+ );
357
+ }
358
+ ```
359
+
360
+ ```css
361
+ /* components/atoms/Badge.module.css */
362
+ .badge {
363
+ display: inline-flex;
364
+ align-items: center;
365
+ gap: var(--space-2);
366
+ padding: var(--space-1) var(--space-3);
367
+ border-radius: var(--radius-full);
368
+ font-size: var(--text-caption);
369
+ font-weight: 600;
370
+ letter-spacing: var(--tracking-wide);
371
+ text-transform: uppercase;
372
+ background: var(--color-accent-subtle);
373
+ color: var(--color-accent);
374
+ }
375
+ ```
376
+
377
+ #### Heading
378
+ ```tsx
379
+ // components/atoms/Heading.tsx
380
+ import styles from './Heading.module.css';
381
+ import { cn } from '@/lib/cn';
382
+
383
+ type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
384
+
385
+ interface HeadingProps {
386
+ level: HeadingLevel;
387
+ as?: HeadingLevel;
388
+ children: React.ReactNode;
389
+ className?: string;
390
+ }
391
+
392
+ export default function Heading({ level, as, children, className }: HeadingProps) {
393
+ const Tag = `h${as ?? level}` as keyof JSX.IntrinsicElements;
394
+ return <Tag className={cn(styles[`h${level}`], className)}>{children}</Tag>;
395
+ }
396
+ ```
397
+
398
+ ```css
399
+ /* components/atoms/Heading.module.css */
400
+ .h1 {
401
+ font-family: var(--font-display);
402
+ font-size: var(--text-h1);
403
+ font-weight: 800;
404
+ line-height: var(--leading-tight);
405
+ letter-spacing: var(--tracking-tight);
406
+ color: var(--color-on-surface);
407
+ }
408
+ .h2 {
409
+ font-family: var(--font-display);
410
+ font-size: var(--text-h2);
411
+ font-weight: 700;
412
+ line-height: var(--leading-tight);
413
+ letter-spacing: var(--tracking-tight);
414
+ color: var(--color-on-surface);
415
+ }
416
+ .h3 {
417
+ font-family: var(--font-display);
418
+ font-size: var(--text-h3);
419
+ font-weight: 700;
420
+ line-height: var(--leading-tight);
421
+ color: var(--color-on-surface);
422
+ }
423
+ .h4, .h5, .h6 {
424
+ font-family: var(--font-display);
425
+ font-size: var(--text-body);
426
+ font-weight: 600;
427
+ line-height: var(--leading-tight);
428
+ color: var(--color-on-surface);
429
+ }
430
+ ```
431
+
432
+ #### Text
433
+ ```tsx
434
+ // components/atoms/Text.tsx
435
+ import styles from './Text.module.css';
436
+ import { cn } from '@/lib/cn';
437
+
438
+ type TextVariant = 'body' | 'caption' | 'label';
439
+
440
+ interface TextProps {
441
+ variant?: TextVariant;
442
+ muted?: boolean;
443
+ children: React.ReactNode;
444
+ className?: string;
445
+ }
446
+
447
+ export default function Text({ variant = 'body', muted = false, children, className }: TextProps) {
448
+ return (
449
+ <p className={cn(styles[variant], muted && styles.muted, className)}>
450
+ {children}
451
+ </p>
452
+ );
453
+ }
454
+ ```
455
+
456
+ ```css
457
+ /* components/atoms/Text.module.css */
458
+ .body {
459
+ font-family: var(--font-body);
460
+ font-size: var(--text-body);
461
+ line-height: var(--leading-normal);
462
+ color: var(--color-on-surface);
463
+ }
464
+ .caption {
465
+ font-family: var(--font-body);
466
+ font-size: var(--text-caption);
467
+ line-height: var(--leading-normal);
468
+ color: var(--color-on-surface-muted);
469
+ }
470
+ .label {
471
+ font-family: var(--font-body);
472
+ font-size: var(--text-caption);
473
+ font-weight: 600;
474
+ letter-spacing: var(--tracking-widest);
475
+ text-transform: uppercase;
476
+ color: var(--color-on-surface-muted);
477
+ }
478
+ .muted { color: var(--color-on-surface-muted); }
479
+ ```
480
+
481
+ #### Input
482
+ ```tsx
483
+ // components/atoms/Input.tsx
484
+ import { type InputHTMLAttributes, forwardRef } from 'react';
485
+ import styles from './Input.module.css';
486
+ import { cn } from '@/lib/cn';
487
+
488
+ interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
489
+ label?: string;
490
+ error?: string;
491
+ }
492
+
493
+ const Input = forwardRef<HTMLInputElement, InputProps>(
494
+ ({ label, error, className, id, ...props }, ref) => {
495
+ const inputId = id ?? label?.toLowerCase().replace(/\s+/g, '-');
496
+ return (
497
+ <div className={styles.wrapper}>
498
+ {label && <label htmlFor={inputId} className={styles.label}>{label}</label>}
499
+ <input
500
+ ref={ref}
501
+ id={inputId}
502
+ className={cn(styles.input, error && styles.error, className)}
503
+ aria-invalid={error ? 'true' : undefined}
504
+ aria-describedby={error ? `${inputId}-error` : undefined}
505
+ {...props}
506
+ />
507
+ {error && <span id={`${inputId}-error`} className={styles.errorText} role="alert">{error}</span>}
508
+ </div>
509
+ );
510
+ }
511
+ );
512
+
513
+ Input.displayName = 'Input';
514
+ export default Input;
515
+ ```
516
+
517
+ ```css
518
+ /* components/atoms/Input.module.css */
519
+ .wrapper { display: flex; flex-direction: column; gap: var(--space-2); }
520
+ .label {
521
+ font-size: var(--text-small);
522
+ font-weight: 600;
523
+ color: var(--color-on-surface);
524
+ }
525
+ .input {
526
+ padding: var(--space-3) var(--space-4);
527
+ border: 2px solid var(--color-base-100);
528
+ border-radius: var(--radius-sm);
529
+ font-size: var(--text-body);
530
+ font-family: var(--font-body);
531
+ background: var(--color-surface);
532
+ color: var(--color-on-surface);
533
+ transition: border-color var(--duration-fast) var(--ease-out);
534
+ outline: none;
535
+ }
536
+ .input:focus {
537
+ border-color: var(--color-accent);
538
+ box-shadow: 0 0 0 3px var(--color-accent-subtle);
539
+ }
540
+ .error { border-color: oklch(0.55 0.25 25); }
541
+ .errorText { font-size: var(--text-small); color: oklch(0.55 0.25 25); }
542
+ ```
543
+
544
+ #### LazyImage
545
+ ```tsx
546
+ // components/atoms/LazyImage.tsx
547
+ import { useState, useRef, useEffect } from 'react';
548
+ import styles from './LazyImage.module.css';
549
+ import { cn } from '@/lib/cn';
550
+
551
+ interface LazyImageProps {
552
+ src: string;
553
+ alt: string;
554
+ width: number;
555
+ height: number;
556
+ className?: string;
557
+ }
558
+
559
+ export default function LazyImage({ src, alt, width, height, className }: LazyImageProps) {
560
+ const [loaded, setLoaded] = useState(false);
561
+ const imgRef = useRef<HTMLImageElement>(null);
562
+
563
+ useEffect(() => {
564
+ const img = imgRef.current;
565
+ if (!img) return;
566
+
567
+ const observer = new IntersectionObserver(
568
+ ([entry]) => {
569
+ if (entry.isIntersecting) {
570
+ img.src = img.dataset.src!;
571
+ observer.disconnect();
572
+ }
573
+ },
574
+ { rootMargin: '200px' }
575
+ );
576
+
577
+ observer.observe(img);
578
+ return () => observer.disconnect();
579
+ }, []);
580
+
581
+ return (
582
+ <img
583
+ ref={imgRef}
584
+ data-src={src}
585
+ alt={alt}
586
+ width={width}
587
+ height={height}
588
+ onLoad={() => setLoaded(true)}
589
+ className={cn(styles.image, loaded && styles.loaded, className)}
590
+ style={{ aspectRatio: `${width} / ${height}` }}
591
+ />
592
+ );
593
+ }
594
+ ```
595
+
596
+ ```css
597
+ /* components/atoms/LazyImage.module.css */
598
+ .image {
599
+ display: block;
600
+ max-width: 100%;
601
+ height: auto;
602
+ opacity: 0;
603
+ transition: opacity var(--duration-slow) var(--ease-out);
604
+ object-fit: cover;
605
+ }
606
+ .loaded { opacity: 1; }
607
+ ```
608
+
609
+ > **Next.js note:** When using Next.js, prefer the built-in `next/image` component over `LazyImage`. It handles lazy loading, responsive sizing, and format optimization automatically.
610
+
611
+ ---
612
+
613
+ ### Molecules
614
+
615
+ #### FeatureCard
616
+ ```tsx
617
+ // components/molecules/FeatureCard.tsx
618
+ import Heading from '@/components/atoms/Heading';
619
+ import Text from '@/components/atoms/Text';
620
+ import styles from './FeatureCard.module.css';
621
+ import { cn } from '@/lib/cn';
622
+
623
+ interface FeatureCardProps {
624
+ icon: React.ReactNode;
625
+ title: string;
626
+ description: string;
627
+ className?: string;
628
+ }
629
+
630
+ export default function FeatureCard({ icon, title, description, className }: FeatureCardProps) {
631
+ return (
632
+ <article className={cn(styles.card, className)}>
633
+ <div className={styles.icon}>{icon}</div>
634
+ <Heading level={3}>{title}</Heading>
635
+ <Text muted>{description}</Text>
636
+ </article>
637
+ );
638
+ }
639
+ ```
640
+
641
+ ```css
642
+ /* components/molecules/FeatureCard.module.css */
643
+ .card {
644
+ background: var(--color-surface);
645
+ border-radius: var(--radius-lg);
646
+ padding: var(--space-8);
647
+ box-shadow: var(--shadow-md);
648
+ border: 1px solid oklch(0 0 0 / 0.05);
649
+ transition: all var(--duration-normal) var(--ease-out);
650
+ display: flex;
651
+ flex-direction: column;
652
+ gap: var(--space-4);
653
+ }
654
+ .card:hover {
655
+ transform: translateY(-4px);
656
+ box-shadow: var(--shadow-lg);
657
+ }
658
+ .icon {
659
+ width: 48px;
660
+ height: 48px;
661
+ display: flex;
662
+ align-items: center;
663
+ justify-content: center;
664
+ border-radius: var(--radius-md);
665
+ background: var(--color-accent-subtle);
666
+ color: var(--color-accent);
667
+ font-size: 1.5rem;
668
+ }
669
+ ```
670
+
671
+ #### TestimonialCard
672
+ ```tsx
673
+ // components/molecules/TestimonialCard.tsx
674
+ import Text from '@/components/atoms/Text';
675
+ import styles from './TestimonialCard.module.css';
676
+
677
+ interface TestimonialCardProps {
678
+ quote: string;
679
+ author: string;
680
+ role: string;
681
+ avatarUrl?: string;
682
+ }
683
+
684
+ export default function TestimonialCard({ quote, author, role, avatarUrl }: TestimonialCardProps) {
685
+ return (
686
+ <blockquote className={styles.card}>
687
+ <Text className={styles.quote}>&ldquo;{quote}&rdquo;</Text>
688
+ <footer className={styles.footer}>
689
+ {avatarUrl && (
690
+ <img src={avatarUrl} alt={author} className={styles.avatar} width={40} height={40} />
691
+ )}
692
+ <div>
693
+ <strong className={styles.author}>{author}</strong>
694
+ <Text variant="caption">{role}</Text>
695
+ </div>
696
+ </footer>
697
+ </blockquote>
698
+ );
699
+ }
700
+ ```
701
+
702
+ ```css
703
+ /* components/molecules/TestimonialCard.module.css */
704
+ .card {
705
+ background: var(--color-surface);
706
+ border-radius: var(--radius-lg);
707
+ padding: var(--space-8);
708
+ box-shadow: var(--shadow-md);
709
+ border: 1px solid oklch(0 0 0 / 0.05);
710
+ display: flex;
711
+ flex-direction: column;
712
+ gap: var(--space-6);
713
+ margin: 0;
714
+ }
715
+ .quote {
716
+ font-size: var(--text-body);
717
+ line-height: var(--leading-relaxed);
718
+ font-style: italic;
719
+ }
720
+ .footer { display: flex; align-items: center; gap: var(--space-3); }
721
+ .avatar {
722
+ width: 40px;
723
+ height: 40px;
724
+ border-radius: var(--radius-full);
725
+ object-fit: cover;
726
+ }
727
+ .author {
728
+ font-size: var(--text-small);
729
+ font-weight: 700;
730
+ color: var(--color-on-surface);
731
+ display: block;
732
+ }
733
+ ```
734
+
735
+ #### PricingCard
736
+ ```tsx
737
+ // components/molecules/PricingCard.tsx
738
+ import Heading from '@/components/atoms/Heading';
739
+ import Text from '@/components/atoms/Text';
740
+ import Badge from '@/components/atoms/Badge';
741
+ import Button from '@/components/atoms/Button';
742
+ import styles from './PricingCard.module.css';
743
+ import { cn } from '@/lib/cn';
744
+
745
+ interface PricingCardProps {
746
+ name: string;
747
+ price: string;
748
+ period?: string;
749
+ description: string;
750
+ features: string[];
751
+ cta: string;
752
+ highlighted?: boolean;
753
+ badge?: string;
754
+ onCtaClick?: () => void;
755
+ }
756
+
757
+ export default function PricingCard({
758
+ name, price, period = '/mo', description, features, cta,
759
+ highlighted = false, badge, onCtaClick,
760
+ }: PricingCardProps) {
761
+ return (
762
+ <article className={cn(styles.card, highlighted && styles.highlighted)}>
763
+ {badge && <Badge className={styles.badge}>{badge}</Badge>}
764
+ <Heading level={3}>{name}</Heading>
765
+ <div className={styles.price}>
766
+ <span className={styles.amount}>{price}</span>
767
+ {period && <span className={styles.period}>{period}</span>}
768
+ </div>
769
+ <Text muted>{description}</Text>
770
+ <ul className={styles.features}>
771
+ {features.map((feature) => (
772
+ <li key={feature} className={styles.feature}>
773
+ <span className={styles.check} aria-hidden="true">&#10003;</span>
774
+ {feature}
775
+ </li>
776
+ ))}
777
+ </ul>
778
+ <Button
779
+ variant={highlighted ? 'primary' : 'secondary'}
780
+ onClick={onCtaClick}
781
+ className={styles.cta}
782
+ >
783
+ {cta}
784
+ </Button>
785
+ </article>
786
+ );
787
+ }
788
+ ```
789
+
790
+ ```css
791
+ /* components/molecules/PricingCard.module.css */
792
+ .card {
793
+ background: var(--color-surface);
794
+ border-radius: var(--radius-lg);
795
+ padding: var(--space-8);
796
+ box-shadow: var(--shadow-md);
797
+ border: 1px solid oklch(0 0 0 / 0.05);
798
+ display: flex;
799
+ flex-direction: column;
800
+ gap: var(--space-4);
801
+ position: relative;
802
+ transition: all var(--duration-normal) var(--ease-out);
803
+ }
804
+ .highlighted {
805
+ border-color: var(--color-accent);
806
+ box-shadow: var(--shadow-lg);
807
+ transform: scale(1.03);
808
+ }
809
+ .badge { position: absolute; top: calc(-1 * var(--space-3)); right: var(--space-6); }
810
+ .price { display: flex; align-items: baseline; gap: var(--space-1); }
811
+ .amount {
812
+ font-family: var(--font-display);
813
+ font-size: var(--text-h1);
814
+ font-weight: 800;
815
+ color: var(--color-on-surface);
816
+ }
817
+ .period { font-size: var(--text-small); color: var(--color-on-surface-muted); }
818
+ .features {
819
+ list-style: none;
820
+ padding: 0;
821
+ margin: var(--space-4) 0;
822
+ display: flex;
823
+ flex-direction: column;
824
+ gap: var(--space-3);
825
+ flex: 1;
826
+ }
827
+ .feature {
828
+ display: flex;
829
+ align-items: center;
830
+ gap: var(--space-2);
831
+ font-size: var(--text-body);
832
+ color: var(--color-on-surface);
833
+ }
834
+ .check { color: var(--color-success); font-weight: 700; }
835
+ .cta { width: 100%; margin-top: auto; }
836
+ ```
837
+
838
+ #### StatCounter (with animated number)
839
+ ```tsx
840
+ // components/molecules/StatCounter.tsx
841
+ import { useCountUp } from '@/hooks/useCountUp';
842
+ import Text from '@/components/atoms/Text';
843
+ import styles from './StatCounter.module.css';
844
+
845
+ interface StatCounterProps {
846
+ value: number;
847
+ suffix?: string;
848
+ prefix?: string;
849
+ label: string;
850
+ }
851
+
852
+ export default function StatCounter({ value, suffix = '', prefix = '', label }: StatCounterProps) {
853
+ const { ref, count } = useCountUp(value);
854
+
855
+ return (
856
+ <div ref={ref} className={styles.stat}>
857
+ <span className={styles.value}>
858
+ {prefix}{count.toLocaleString()}{suffix}
859
+ </span>
860
+ <Text variant="caption" muted>{label}</Text>
861
+ </div>
862
+ );
863
+ }
864
+ ```
865
+
866
+ ```css
867
+ /* components/molecules/StatCounter.module.css */
868
+ .stat {
869
+ display: flex;
870
+ flex-direction: column;
871
+ align-items: center;
872
+ gap: var(--space-2);
873
+ text-align: center;
874
+ }
875
+ .value {
876
+ font-family: var(--font-display);
877
+ font-size: var(--text-h1);
878
+ font-weight: 800;
879
+ color: var(--color-accent);
880
+ line-height: 1;
881
+ }
882
+ ```
883
+
884
+ #### CTAGroup
885
+ ```tsx
886
+ // components/molecules/CTAGroup.tsx
887
+ import Button from '@/components/atoms/Button';
888
+ import styles from './CTAGroup.module.css';
889
+ import { cn } from '@/lib/cn';
890
+
891
+ interface CTAGroupProps {
892
+ primaryLabel: string;
893
+ primaryHref?: string;
894
+ secondaryLabel?: string;
895
+ secondaryHref?: string;
896
+ onPrimaryClick?: () => void;
897
+ onSecondaryClick?: () => void;
898
+ className?: string;
899
+ }
900
+
901
+ export default function CTAGroup({
902
+ primaryLabel, primaryHref, secondaryLabel, secondaryHref,
903
+ onPrimaryClick, onSecondaryClick, className,
904
+ }: CTAGroupProps) {
905
+ return (
906
+ <div className={cn(styles.group, className)}>
907
+ {primaryHref ? (
908
+ <a href={primaryHref}><Button variant="primary" size="lg">{primaryLabel}</Button></a>
909
+ ) : (
910
+ <Button variant="primary" size="lg" onClick={onPrimaryClick}>{primaryLabel}</Button>
911
+ )}
912
+ {secondaryLabel && (
913
+ secondaryHref ? (
914
+ <a href={secondaryHref}><Button variant="secondary" size="lg">{secondaryLabel}</Button></a>
915
+ ) : (
916
+ <Button variant="secondary" size="lg" onClick={onSecondaryClick}>{secondaryLabel}</Button>
917
+ )
918
+ )}
919
+ </div>
920
+ );
921
+ }
922
+ ```
923
+
924
+ ```css
925
+ /* components/molecules/CTAGroup.module.css */
926
+ .group {
927
+ display: flex;
928
+ align-items: center;
929
+ gap: var(--space-4);
930
+ flex-wrap: wrap;
931
+ }
932
+ ```
933
+
934
+ ---
935
+
936
+ ### Organisms
937
+
938
+ #### Layout Primitives
939
+
940
+ ```tsx
941
+ // components/ui/Container.tsx
942
+ import styles from './Container.module.css';
943
+ import { cn } from '@/lib/cn';
944
+
945
+ interface ContainerProps {
946
+ narrow?: boolean;
947
+ children: React.ReactNode;
948
+ className?: string;
949
+ }
950
+
951
+ export default function Container({ narrow = false, children, className }: ContainerProps) {
952
+ return (
953
+ <div className={cn(styles.container, narrow && styles.narrow, className)}>
954
+ {children}
955
+ </div>
956
+ );
957
+ }
958
+ ```
959
+
960
+ ```css
961
+ /* components/ui/Container.module.css */
962
+ .container {
963
+ max-width: var(--container-max);
964
+ margin: 0 auto;
965
+ padding: 0 var(--section-px);
966
+ }
967
+ .narrow { max-width: var(--container-narrow); }
968
+ ```
969
+
970
+ ```tsx
971
+ // components/ui/Section.tsx
972
+ import styles from './Section.module.css';
973
+ import { cn } from '@/lib/cn';
974
+
975
+ type SectionBackground = 'default' | 'alt' | 'dark';
976
+
977
+ interface SectionProps {
978
+ bg?: SectionBackground;
979
+ id?: string;
980
+ children: React.ReactNode;
981
+ className?: string;
982
+ }
983
+
984
+ export default function Section({ bg = 'default', id, children, className }: SectionProps) {
985
+ return (
986
+ <section id={id} className={cn(styles.section, styles[bg], className)}>
987
+ {children}
988
+ </section>
989
+ );
990
+ }
991
+ ```
992
+
993
+ ```css
994
+ /* components/ui/Section.module.css */
995
+ .section {
996
+ padding: var(--section-py) 0;
997
+ }
998
+ .default { background: var(--color-surface); color: var(--color-on-surface); }
999
+ .alt { background: var(--color-surface-alt); color: var(--color-on-surface); }
1000
+ .dark { background: var(--color-surface-dark); color: var(--color-on-dark); }
1001
+ ```
1002
+
1003
+ #### Navbar
1004
+ ```tsx
1005
+ // components/organisms/Navbar.tsx
1006
+ import { useState } from 'react';
1007
+ import Button from '@/components/atoms/Button';
1008
+ import Container from '@/components/ui/Container';
1009
+ import styles from './Navbar.module.css';
1010
+ import { cn } from '@/lib/cn';
1011
+
1012
+ interface NavLink {
1013
+ label: string;
1014
+ href: string;
1015
+ }
1016
+
1017
+ interface NavbarProps {
1018
+ logo: React.ReactNode;
1019
+ links: NavLink[];
1020
+ ctaLabel?: string;
1021
+ ctaHref?: string;
1022
+ }
1023
+
1024
+ export default function Navbar({ logo, links, ctaLabel, ctaHref }: NavbarProps) {
1025
+ const [open, setOpen] = useState(false);
1026
+
1027
+ return (
1028
+ <header className={styles.header}>
1029
+ <Container>
1030
+ <nav className={styles.nav} aria-label="Main navigation">
1031
+ <div className={styles.logo}>{logo}</div>
1032
+
1033
+ <button
1034
+ className={styles.toggle}
1035
+ onClick={() => setOpen(!open)}
1036
+ aria-expanded={open}
1037
+ aria-label="Toggle menu"
1038
+ >
1039
+ <span className={cn(styles.bar, open && styles.open)} />
1040
+ </button>
1041
+
1042
+ <div className={cn(styles.menu, open && styles.menuOpen)}>
1043
+ <ul className={styles.links}>
1044
+ {links.map(({ label, href }) => (
1045
+ <li key={href}>
1046
+ <a href={href} className={styles.link}>{label}</a>
1047
+ </li>
1048
+ ))}
1049
+ </ul>
1050
+ {ctaLabel && ctaHref && (
1051
+ <a href={ctaHref}>
1052
+ <Button variant="primary" size="sm">{ctaLabel}</Button>
1053
+ </a>
1054
+ )}
1055
+ </div>
1056
+ </nav>
1057
+ </Container>
1058
+ </header>
1059
+ );
1060
+ }
1061
+ ```
1062
+
1063
+ ```css
1064
+ /* components/organisms/Navbar.module.css */
1065
+ .header {
1066
+ position: sticky;
1067
+ top: 0;
1068
+ z-index: 100;
1069
+ background: oklch(1 0 0 / 0.85);
1070
+ backdrop-filter: blur(12px);
1071
+ border-bottom: 1px solid oklch(0 0 0 / 0.05);
1072
+ }
1073
+ .nav {
1074
+ display: flex;
1075
+ align-items: center;
1076
+ justify-content: space-between;
1077
+ height: 64px;
1078
+ }
1079
+ .logo { font-weight: 800; font-size: var(--text-h3); }
1080
+ .menu { display: flex; align-items: center; gap: var(--space-8); }
1081
+ .links {
1082
+ display: flex;
1083
+ list-style: none;
1084
+ gap: var(--space-6);
1085
+ margin: 0;
1086
+ padding: 0;
1087
+ }
1088
+ .link {
1089
+ font-size: var(--text-small);
1090
+ font-weight: 500;
1091
+ color: var(--color-on-surface-muted);
1092
+ text-decoration: none;
1093
+ transition: color var(--duration-fast) var(--ease-out);
1094
+ }
1095
+ .link:hover { color: var(--color-accent); }
1096
+ .toggle {
1097
+ display: none;
1098
+ background: none;
1099
+ border: none;
1100
+ cursor: pointer;
1101
+ padding: var(--space-2);
1102
+ }
1103
+ .bar {
1104
+ display: block;
1105
+ width: 24px;
1106
+ height: 2px;
1107
+ background: var(--color-on-surface);
1108
+ position: relative;
1109
+ transition: background var(--duration-fast);
1110
+ }
1111
+ .bar::before, .bar::after {
1112
+ content: '';
1113
+ display: block;
1114
+ width: 24px;
1115
+ height: 2px;
1116
+ background: var(--color-on-surface);
1117
+ position: absolute;
1118
+ transition: transform var(--duration-normal) var(--ease-out);
1119
+ }
1120
+ .bar::before { top: -7px; }
1121
+ .bar::after { top: 7px; }
1122
+ .open { background: transparent; }
1123
+ .open::before { transform: rotate(45deg) translate(5px, 5px); }
1124
+ .open::after { transform: rotate(-45deg) translate(5px, -5px); }
1125
+
1126
+ @media (max-width: 768px) {
1127
+ .toggle { display: block; }
1128
+ .menu {
1129
+ display: none;
1130
+ position: absolute;
1131
+ top: 64px;
1132
+ left: 0;
1133
+ right: 0;
1134
+ flex-direction: column;
1135
+ background: var(--color-surface);
1136
+ padding: var(--space-6);
1137
+ border-bottom: 1px solid oklch(0 0 0 / 0.05);
1138
+ box-shadow: var(--shadow-lg);
1139
+ }
1140
+ .menuOpen { display: flex; }
1141
+ .links { flex-direction: column; align-items: center; }
1142
+ }
1143
+ ```
1144
+
1145
+ #### HeroSection
1146
+ ```tsx
1147
+ // components/organisms/HeroSection.tsx
1148
+ import Section from '@/components/ui/Section';
1149
+ import Container from '@/components/ui/Container';
1150
+ import Badge from '@/components/atoms/Badge';
1151
+ import Heading from '@/components/atoms/Heading';
1152
+ import Text from '@/components/atoms/Text';
1153
+ import CTAGroup from '@/components/molecules/CTAGroup';
1154
+ import styles from './HeroSection.module.css';
1155
+
1156
+ interface HeroSectionProps {
1157
+ badge?: string;
1158
+ title: string;
1159
+ subtitle: string;
1160
+ primaryCta: string;
1161
+ primaryHref: string;
1162
+ secondaryCta?: string;
1163
+ secondaryHref?: string;
1164
+ image?: React.ReactNode;
1165
+ }
1166
+
1167
+ export default function HeroSection({
1168
+ badge, title, subtitle, primaryCta, primaryHref,
1169
+ secondaryCta, secondaryHref, image,
1170
+ }: HeroSectionProps) {
1171
+ return (
1172
+ <Section bg="dark" className={styles.hero}>
1173
+ <Container>
1174
+ <div className={styles.grid}>
1175
+ <div className={styles.content}>
1176
+ {badge && <Badge>{badge}</Badge>}
1177
+ <h1 className={styles.title}>{title}</h1>
1178
+ <Text className={styles.subtitle}>{subtitle}</Text>
1179
+ <CTAGroup
1180
+ primaryLabel={primaryCta}
1181
+ primaryHref={primaryHref}
1182
+ secondaryLabel={secondaryCta}
1183
+ secondaryHref={secondaryHref}
1184
+ />
1185
+ </div>
1186
+ {image && <div className={styles.visual}>{image}</div>}
1187
+ </div>
1188
+ </Container>
1189
+ </Section>
1190
+ );
1191
+ }
1192
+ ```
1193
+
1194
+ ```css
1195
+ /* components/organisms/HeroSection.module.css */
1196
+ .hero { padding-top: var(--space-24); padding-bottom: var(--space-24); }
1197
+ .grid {
1198
+ display: grid;
1199
+ grid-template-columns: 1fr;
1200
+ gap: var(--space-12);
1201
+ align-items: center;
1202
+ }
1203
+ .content {
1204
+ display: flex;
1205
+ flex-direction: column;
1206
+ gap: var(--space-6);
1207
+ }
1208
+ .title {
1209
+ font-family: var(--font-display);
1210
+ font-size: var(--text-display);
1211
+ font-weight: 800;
1212
+ line-height: var(--leading-tight);
1213
+ letter-spacing: var(--tracking-tight);
1214
+ color: var(--color-on-dark);
1215
+ margin: 0;
1216
+ }
1217
+ .subtitle {
1218
+ font-size: var(--text-body);
1219
+ line-height: var(--leading-relaxed);
1220
+ color: var(--color-on-dark);
1221
+ opacity: 0.8;
1222
+ max-width: 540px;
1223
+ }
1224
+ .visual {
1225
+ display: flex;
1226
+ justify-content: center;
1227
+ }
1228
+
1229
+ @media (min-width: 1024px) {
1230
+ .grid { grid-template-columns: 1.2fr 0.8fr; }
1231
+ }
1232
+ ```
1233
+
1234
+ #### FeaturesGrid
1235
+ ```tsx
1236
+ // components/organisms/FeaturesGrid.tsx
1237
+ import Section from '@/components/ui/Section';
1238
+ import Container from '@/components/ui/Container';
1239
+ import Heading from '@/components/atoms/Heading';
1240
+ import Text from '@/components/atoms/Text';
1241
+ import FeatureCard from '@/components/molecules/FeatureCard';
1242
+ import { useScrollReveal } from '@/hooks/useIntersectionObserver';
1243
+ import styles from './FeaturesGrid.module.css';
1244
+
1245
+ interface Feature {
1246
+ icon: React.ReactNode;
1247
+ title: string;
1248
+ description: string;
1249
+ }
1250
+
1251
+ interface FeaturesGridProps {
1252
+ badge?: string;
1253
+ title: string;
1254
+ subtitle?: string;
1255
+ features: Feature[];
1256
+ }
1257
+
1258
+ export default function FeaturesGrid({ badge, title, subtitle, features }: FeaturesGridProps) {
1259
+ const { ref, isVisible } = useScrollReveal();
1260
+
1261
+ return (
1262
+ <Section bg="alt" id="features">
1263
+ <Container>
1264
+ <div className={styles.header}>
1265
+ {badge && <span className={styles.badge}>{badge}</span>}
1266
+ <Heading level={2}>{title}</Heading>
1267
+ {subtitle && <Text muted>{subtitle}</Text>}
1268
+ </div>
1269
+ <div ref={ref} className={styles.grid}>
1270
+ {features.map((feature, i) => (
1271
+ <div
1272
+ key={feature.title}
1273
+ className={styles.item}
1274
+ style={{
1275
+ opacity: isVisible ? 1 : 0,
1276
+ transform: isVisible ? 'translateY(0)' : 'translateY(24px)',
1277
+ transition: `all var(--duration-slow) var(--ease-out)`,
1278
+ transitionDelay: `${i * 80}ms`,
1279
+ }}
1280
+ >
1281
+ <FeatureCard {...feature} />
1282
+ </div>
1283
+ ))}
1284
+ </div>
1285
+ </Container>
1286
+ </Section>
1287
+ );
1288
+ }
1289
+ ```
1290
+
1291
+ ```css
1292
+ /* components/organisms/FeaturesGrid.module.css */
1293
+ .header {
1294
+ text-align: center;
1295
+ max-width: var(--container-narrow);
1296
+ margin: 0 auto var(--space-12);
1297
+ display: flex;
1298
+ flex-direction: column;
1299
+ gap: var(--space-4);
1300
+ }
1301
+ .badge {
1302
+ display: inline-flex;
1303
+ align-self: center;
1304
+ padding: var(--space-1) var(--space-3);
1305
+ border-radius: var(--radius-full);
1306
+ font-size: var(--text-caption);
1307
+ font-weight: 600;
1308
+ letter-spacing: var(--tracking-wide);
1309
+ text-transform: uppercase;
1310
+ background: var(--color-accent-subtle);
1311
+ color: var(--color-accent);
1312
+ }
1313
+ .grid {
1314
+ display: grid;
1315
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
1316
+ gap: var(--space-8);
1317
+ }
1318
+ .item { will-change: transform, opacity; }
1319
+ ```
1320
+
1321
+ #### FAQAccordion
1322
+ ```tsx
1323
+ // components/organisms/FAQAccordion.tsx
1324
+ import { useState, useId } from 'react';
1325
+ import Section from '@/components/ui/Section';
1326
+ import Container from '@/components/ui/Container';
1327
+ import Heading from '@/components/atoms/Heading';
1328
+ import styles from './FAQAccordion.module.css';
1329
+
1330
+ interface FAQItem {
1331
+ question: string;
1332
+ answer: string;
1333
+ }
1334
+
1335
+ interface FAQAccordionProps {
1336
+ title: string;
1337
+ items: FAQItem[];
1338
+ }
1339
+
1340
+ function FAQEntry({ question, answer }: FAQItem) {
1341
+ const [open, setOpen] = useState(false);
1342
+ const id = useId();
1343
+
1344
+ return (
1345
+ <div className={styles.item}>
1346
+ <button
1347
+ className={styles.trigger}
1348
+ onClick={() => setOpen(!open)}
1349
+ aria-expanded={open}
1350
+ aria-controls={`${id}-panel`}
1351
+ id={`${id}-trigger`}
1352
+ >
1353
+ <span className={styles.question}>{question}</span>
1354
+ <span className={styles.icon} aria-hidden="true">{open ? '\u2212' : '+'}</span>
1355
+ </button>
1356
+ <div
1357
+ id={`${id}-panel`}
1358
+ role="region"
1359
+ aria-labelledby={`${id}-trigger`}
1360
+ className={styles.panel}
1361
+ style={{
1362
+ maxHeight: open ? '500px' : '0',
1363
+ opacity: open ? 1 : 0,
1364
+ paddingBottom: open ? 'var(--space-6)' : '0',
1365
+ }}
1366
+ >
1367
+ <p className={styles.answer}>{answer}</p>
1368
+ </div>
1369
+ </div>
1370
+ );
1371
+ }
1372
+
1373
+ export default function FAQAccordion({ title, items }: FAQAccordionProps) {
1374
+ return (
1375
+ <Section bg="alt" id="faq">
1376
+ <Container narrow>
1377
+ <Heading level={2} className={styles.title}>{title}</Heading>
1378
+ <div className={styles.list}>
1379
+ {items.map((item) => (
1380
+ <FAQEntry key={item.question} {...item} />
1381
+ ))}
1382
+ </div>
1383
+ </Container>
1384
+ </Section>
1385
+ );
1386
+ }
1387
+ ```
1388
+
1389
+ ```css
1390
+ /* components/organisms/FAQAccordion.module.css */
1391
+ .title { text-align: center; margin-bottom: var(--space-12); }
1392
+ .list {
1393
+ display: flex;
1394
+ flex-direction: column;
1395
+ gap: var(--space-2);
1396
+ }
1397
+ .item {
1398
+ border-bottom: 1px solid oklch(0 0 0 / 0.08);
1399
+ }
1400
+ .trigger {
1401
+ width: 100%;
1402
+ display: flex;
1403
+ justify-content: space-between;
1404
+ align-items: center;
1405
+ padding: var(--space-6) 0;
1406
+ background: none;
1407
+ border: none;
1408
+ cursor: pointer;
1409
+ text-align: left;
1410
+ }
1411
+ .question {
1412
+ font-family: var(--font-display);
1413
+ font-size: var(--text-h3);
1414
+ font-weight: 600;
1415
+ color: var(--color-on-surface);
1416
+ }
1417
+ .icon {
1418
+ font-size: 1.5rem;
1419
+ color: var(--color-accent);
1420
+ flex-shrink: 0;
1421
+ margin-left: var(--space-4);
1422
+ }
1423
+ .panel {
1424
+ overflow: hidden;
1425
+ transition: max-height var(--duration-normal) var(--ease-out),
1426
+ opacity var(--duration-normal) var(--ease-out),
1427
+ padding-bottom var(--duration-normal) var(--ease-out);
1428
+ }
1429
+ .answer {
1430
+ font-size: var(--text-body);
1431
+ line-height: var(--leading-relaxed);
1432
+ color: var(--color-on-surface-muted);
1433
+ margin: 0;
1434
+ }
1435
+ ```
1436
+
1437
+ #### CTASection
1438
+ ```tsx
1439
+ // components/organisms/CTASection.tsx
1440
+ import Section from '@/components/ui/Section';
1441
+ import Container from '@/components/ui/Container';
1442
+ import Heading from '@/components/atoms/Heading';
1443
+ import Text from '@/components/atoms/Text';
1444
+ import CTAGroup from '@/components/molecules/CTAGroup';
1445
+ import styles from './CTASection.module.css';
1446
+
1447
+ interface CTASectionProps {
1448
+ title: string;
1449
+ subtitle: string;
1450
+ primaryLabel: string;
1451
+ primaryHref: string;
1452
+ secondaryLabel?: string;
1453
+ secondaryHref?: string;
1454
+ }
1455
+
1456
+ export default function CTASection({
1457
+ title, subtitle, primaryLabel, primaryHref,
1458
+ secondaryLabel, secondaryHref,
1459
+ }: CTASectionProps) {
1460
+ return (
1461
+ <Section bg="dark" className={styles.cta}>
1462
+ <Container narrow>
1463
+ <div className={styles.content}>
1464
+ <Heading level={2} className={styles.title}>{title}</Heading>
1465
+ <Text className={styles.subtitle}>{subtitle}</Text>
1466
+ <CTAGroup
1467
+ primaryLabel={primaryLabel}
1468
+ primaryHref={primaryHref}
1469
+ secondaryLabel={secondaryLabel}
1470
+ secondaryHref={secondaryHref}
1471
+ className={styles.actions}
1472
+ />
1473
+ </div>
1474
+ </Container>
1475
+ </Section>
1476
+ );
1477
+ }
1478
+ ```
1479
+
1480
+ ```css
1481
+ /* components/organisms/CTASection.module.css */
1482
+ .cta {
1483
+ background: linear-gradient(135deg, var(--color-base-900), var(--color-base-800));
1484
+ }
1485
+ .content {
1486
+ text-align: center;
1487
+ display: flex;
1488
+ flex-direction: column;
1489
+ align-items: center;
1490
+ gap: var(--space-6);
1491
+ }
1492
+ .title { color: var(--color-on-dark); }
1493
+ .subtitle { color: var(--color-on-dark); opacity: 0.8; }
1494
+ .actions { justify-content: center; }
1495
+ ```
1496
+
1497
+ #### Footer
1498
+ ```tsx
1499
+ // components/organisms/Footer.tsx
1500
+ import Container from '@/components/ui/Container';
1501
+ import Text from '@/components/atoms/Text';
1502
+ import styles from './Footer.module.css';
1503
+
1504
+ interface FooterLink {
1505
+ label: string;
1506
+ href: string;
1507
+ }
1508
+
1509
+ interface FooterColumn {
1510
+ title: string;
1511
+ links: FooterLink[];
1512
+ }
1513
+
1514
+ interface FooterProps {
1515
+ logo: React.ReactNode;
1516
+ columns: FooterColumn[];
1517
+ copyright: string;
1518
+ }
1519
+
1520
+ export default function Footer({ logo, columns, copyright }: FooterProps) {
1521
+ return (
1522
+ <footer className={styles.footer}>
1523
+ <Container>
1524
+ <div className={styles.grid}>
1525
+ <div className={styles.brand}>{logo}</div>
1526
+ {columns.map((col) => (
1527
+ <nav key={col.title} className={styles.column} aria-label={col.title}>
1528
+ <h4 className={styles.columnTitle}>{col.title}</h4>
1529
+ <ul className={styles.links}>
1530
+ {col.links.map(({ label, href }) => (
1531
+ <li key={href}>
1532
+ <a href={href} className={styles.link}>{label}</a>
1533
+ </li>
1534
+ ))}
1535
+ </ul>
1536
+ </nav>
1537
+ ))}
1538
+ </div>
1539
+ <div className={styles.bottom}>
1540
+ <Text variant="caption" muted>{copyright}</Text>
1541
+ </div>
1542
+ </Container>
1543
+ </footer>
1544
+ );
1545
+ }
1546
+ ```
1547
+
1548
+ ```css
1549
+ /* components/organisms/Footer.module.css */
1550
+ .footer {
1551
+ background: var(--color-base-900);
1552
+ padding: var(--space-16) 0 var(--space-8);
1553
+ color: var(--color-on-dark);
1554
+ }
1555
+ .grid {
1556
+ display: grid;
1557
+ grid-template-columns: 1.5fr repeat(auto-fit, minmax(150px, 1fr));
1558
+ gap: var(--space-12);
1559
+ margin-bottom: var(--space-12);
1560
+ }
1561
+ .brand { font-weight: 800; font-size: var(--text-h3); }
1562
+ .columnTitle {
1563
+ font-size: var(--text-small);
1564
+ font-weight: 600;
1565
+ text-transform: uppercase;
1566
+ letter-spacing: var(--tracking-widest);
1567
+ margin: 0 0 var(--space-4);
1568
+ color: var(--color-on-dark);
1569
+ }
1570
+ .links { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: var(--space-3); }
1571
+ .link {
1572
+ font-size: var(--text-small);
1573
+ color: var(--color-on-dark);
1574
+ opacity: 0.6;
1575
+ text-decoration: none;
1576
+ transition: opacity var(--duration-fast);
1577
+ }
1578
+ .link:hover { opacity: 1; }
1579
+ .bottom {
1580
+ border-top: 1px solid oklch(1 0 0 / 0.1);
1581
+ padding-top: var(--space-8);
1582
+ text-align: center;
1583
+ }
1584
+
1585
+ @media (max-width: 768px) {
1586
+ .grid { grid-template-columns: 1fr 1fr; }
1587
+ .brand { grid-column: 1 / -1; }
1588
+ }
1589
+ ```
1590
+
1591
+ ---
1592
+
1593
+ ## Custom Hooks
1594
+
1595
+ ### useIntersectionObserver (scroll reveals)
1596
+ ```ts
1597
+ // hooks/useIntersectionObserver.ts
1598
+ import { useRef, useState, useEffect, type RefObject } from 'react';
1599
+
1600
+ interface UseScrollRevealOptions {
1601
+ threshold?: number;
1602
+ rootMargin?: string;
1603
+ triggerOnce?: boolean;
1604
+ }
1605
+
1606
+ export function useScrollReveal<T extends HTMLElement = HTMLDivElement>(
1607
+ options: UseScrollRevealOptions = {}
1608
+ ): { ref: RefObject<T | null>; isVisible: boolean } {
1609
+ const { threshold = 0.15, rootMargin = '0px', triggerOnce = true } = options;
1610
+ const ref = useRef<T>(null);
1611
+ const [isVisible, setIsVisible] = useState(false);
1612
+
1613
+ useEffect(() => {
1614
+ const el = ref.current;
1615
+ if (!el) return;
1616
+
1617
+ const observer = new IntersectionObserver(
1618
+ ([entry]) => {
1619
+ if (entry.isIntersecting) {
1620
+ setIsVisible(true);
1621
+ if (triggerOnce) observer.disconnect();
1622
+ } else if (!triggerOnce) {
1623
+ setIsVisible(false);
1624
+ }
1625
+ },
1626
+ { threshold, rootMargin }
1627
+ );
1628
+
1629
+ observer.observe(el);
1630
+ return () => observer.disconnect();
1631
+ }, [threshold, rootMargin, triggerOnce]);
1632
+
1633
+ return { ref, isVisible };
1634
+ }
1635
+ ```
1636
+
1637
+ ### useCountUp (animated numbers)
1638
+ ```ts
1639
+ // hooks/useCountUp.ts
1640
+ import { useRef, useState, useEffect, type RefObject } from 'react';
1641
+ import { useScrollReveal } from './useIntersectionObserver';
1642
+
1643
+ interface UseCountUpResult {
1644
+ ref: RefObject<HTMLDivElement | null>;
1645
+ count: number;
1646
+ }
1647
+
1648
+ export function useCountUp(target: number, duration = 2000): UseCountUpResult {
1649
+ const { ref, isVisible } = useScrollReveal<HTMLDivElement>();
1650
+ const [count, setCount] = useState(0);
1651
+ const hasAnimated = useRef(false);
1652
+
1653
+ useEffect(() => {
1654
+ if (!isVisible || hasAnimated.current) return;
1655
+ hasAnimated.current = true;
1656
+
1657
+ const startTime = performance.now();
1658
+
1659
+ function animate(now: number) {
1660
+ const elapsed = now - startTime;
1661
+ const progress = Math.min(elapsed / duration, 1);
1662
+ // Ease-out quad
1663
+ const eased = 1 - (1 - progress) * (1 - progress);
1664
+ setCount(Math.round(eased * target));
1665
+
1666
+ if (progress < 1) {
1667
+ requestAnimationFrame(animate);
1668
+ }
1669
+ }
1670
+
1671
+ requestAnimationFrame(animate);
1672
+ }, [isVisible, target, duration]);
1673
+
1674
+ return { ref, count };
1675
+ }
1676
+ ```
1677
+
1678
+ ### useMediaQuery (responsive hooks)
1679
+ ```ts
1680
+ // hooks/useMediaQuery.ts
1681
+ import { useState, useEffect } from 'react';
1682
+
1683
+ export function useMediaQuery(query: string): boolean {
1684
+ const [matches, setMatches] = useState(false);
1685
+
1686
+ useEffect(() => {
1687
+ const mql = window.matchMedia(query);
1688
+ setMatches(mql.matches);
1689
+
1690
+ function onChange(e: MediaQueryListEvent) {
1691
+ setMatches(e.matches);
1692
+ }
1693
+
1694
+ mql.addEventListener('change', onChange);
1695
+ return () => mql.removeEventListener('change', onChange);
1696
+ }, [query]);
1697
+
1698
+ return matches;
1699
+ }
1700
+
1701
+ // Convenience hooks using the same breakpoints as CSS
1702
+ export const useIsMobile = () => useMediaQuery('(max-width: 767px)');
1703
+ export const useIsTablet = () => useMediaQuery('(min-width: 768px) and (max-width: 1023px)');
1704
+ export const useIsDesktop = () => useMediaQuery('(min-width: 1024px)');
1705
+ ```
1706
+
1707
+ ---
1708
+
1709
+ ## Animation Patterns
1710
+
1711
+ ### CSS-Only Animations (default, no dependencies)
1712
+
1713
+ ```css
1714
+ /* globals.css — add to design tokens file */
1715
+
1716
+ /* Scroll reveal base */
1717
+ .reveal {
1718
+ opacity: 0;
1719
+ transform: translateY(24px);
1720
+ transition:
1721
+ opacity var(--duration-slow) var(--ease-out),
1722
+ transform var(--duration-slow) var(--ease-out);
1723
+ }
1724
+ .reveal.visible {
1725
+ opacity: 1;
1726
+ transform: translateY(0);
1727
+ }
1728
+
1729
+ /* Stagger children */
1730
+ .stagger > * {
1731
+ transition-delay: calc(var(--index, 0) * 80ms);
1732
+ }
1733
+
1734
+ /* Reduced motion */
1735
+ @media (prefers-reduced-motion: reduce) {
1736
+ .reveal,
1737
+ .reveal.visible {
1738
+ transition: none;
1739
+ opacity: 1;
1740
+ transform: none;
1741
+ }
1742
+ }
1743
+ ```
1744
+
1745
+ ### React component for scroll reveal
1746
+ ```tsx
1747
+ // components/ui/Reveal.tsx
1748
+ import { useScrollReveal } from '@/hooks/useIntersectionObserver';
1749
+ import { cn } from '@/lib/cn';
1750
+ import styles from './Reveal.module.css';
1751
+
1752
+ interface RevealProps {
1753
+ children: React.ReactNode;
1754
+ className?: string;
1755
+ delay?: number;
1756
+ }
1757
+
1758
+ export default function Reveal({ children, className, delay = 0 }: RevealProps) {
1759
+ const { ref, isVisible } = useScrollReveal();
1760
+
1761
+ return (
1762
+ <div
1763
+ ref={ref}
1764
+ className={cn(styles.reveal, isVisible && styles.visible, className)}
1765
+ style={{ transitionDelay: `${delay}ms` }}
1766
+ >
1767
+ {children}
1768
+ </div>
1769
+ );
1770
+ }
1771
+ ```
1772
+
1773
+ ```css
1774
+ /* components/ui/Reveal.module.css */
1775
+ .reveal {
1776
+ opacity: 0;
1777
+ transform: translateY(24px);
1778
+ transition:
1779
+ opacity var(--duration-slow) var(--ease-out),
1780
+ transform var(--duration-slow) var(--ease-out);
1781
+ will-change: transform, opacity;
1782
+ }
1783
+ .visible {
1784
+ opacity: 1;
1785
+ transform: translateY(0);
1786
+ }
1787
+
1788
+ @media (prefers-reduced-motion: reduce) {
1789
+ .reveal {
1790
+ opacity: 1;
1791
+ transform: none;
1792
+ transition: none;
1793
+ }
1794
+ }
1795
+ ```
1796
+
1797
+ ### Framer Motion (optional, for complex animations)
1798
+
1799
+ Only add `framer-motion` when CSS-only is insufficient (e.g., layout animations, gesture-based, exit animations).
1800
+
1801
+ ```tsx
1802
+ // Example: stagger list with Framer Motion
1803
+ import { motion } from 'framer-motion';
1804
+
1805
+ const container = {
1806
+ hidden: {},
1807
+ show: { transition: { staggerChildren: 0.08 } },
1808
+ };
1809
+
1810
+ const item = {
1811
+ hidden: { opacity: 0, y: 24 },
1812
+ show: { opacity: 1, y: 0, transition: { duration: 0.4, ease: [0.16, 1, 0.3, 1] } },
1813
+ };
1814
+
1815
+ function AnimatedList({ children }: { children: React.ReactNode[] }) {
1816
+ return (
1817
+ <motion.div variants={container} initial="hidden" whileInView="show" viewport={{ once: true, margin: '-50px' }}>
1818
+ {children.map((child, i) => (
1819
+ <motion.div key={i} variants={item}>{child}</motion.div>
1820
+ ))}
1821
+ </motion.div>
1822
+ );
1823
+ }
1824
+ ```
1825
+
1826
+ ### Animation Rules
1827
+ - Only animate `opacity` and `transform` — never width, height, margin, or padding
1828
+ - Use `will-change: transform, opacity` on animated elements
1829
+ - ALWAYS respect `prefers-reduced-motion`
1830
+ - CSS-only is the default; reach for Framer Motion only when needed
1831
+ - Stagger delay: 60-100ms per item, cap at ~400ms total
1832
+ - Scroll reveal threshold: 10-20% visibility before triggering
1833
+
1834
+ ---
1835
+
1836
+ ## Performance
1837
+
1838
+ ### Image Strategy
1839
+
1840
+ **Next.js:**
1841
+ ```tsx
1842
+ import Image from 'next/image';
1843
+
1844
+ <Image
1845
+ src="/hero-visual.webp"
1846
+ alt="Product screenshot"
1847
+ width={800}
1848
+ height={600}
1849
+ priority // for above-the-fold
1850
+ sizes="(max-width: 768px) 100vw, 50vw"
1851
+ />
1852
+ ```
1853
+
1854
+ **Vite + React:**
1855
+ Use the `LazyImage` atom defined above, or a simple native approach:
1856
+ ```tsx
1857
+ <img
1858
+ src="/hero.webp"
1859
+ alt="Product screenshot"
1860
+ width={800}
1861
+ height={600}
1862
+ loading="lazy"
1863
+ decoding="async"
1864
+ style={{ aspectRatio: '800 / 600' }}
1865
+ />
1866
+ ```
1867
+
1868
+ ### Code Splitting
1869
+ ```tsx
1870
+ import { lazy, Suspense } from 'react';
1871
+
1872
+ // Split heavy below-the-fold sections
1873
+ const PricingSection = lazy(() => import('@/components/organisms/PricingSection'));
1874
+ const TestimonialsSection = lazy(() => import('@/components/organisms/TestimonialsSection'));
1875
+
1876
+ function LandingPage() {
1877
+ return (
1878
+ <>
1879
+ <HeroSection {...heroProps} />
1880
+ <FeaturesGrid {...featuresProps} />
1881
+ <Suspense fallback={<div style={{ minHeight: '400px' }} />}>
1882
+ <PricingSection {...pricingProps} />
1883
+ </Suspense>
1884
+ <Suspense fallback={<div style={{ minHeight: '300px' }} />}>
1885
+ <TestimonialsSection {...testimonialsProps} />
1886
+ </Suspense>
1887
+ <CTASection {...ctaProps} />
1888
+ </>
1889
+ );
1890
+ }
1891
+ ```
1892
+
1893
+ ### Font Loading
1894
+
1895
+ **Next.js (built-in):**
1896
+ ```tsx
1897
+ // app/layout.tsx
1898
+ import { Inter, Plus_Jakarta_Sans } from 'next/font/google';
1899
+
1900
+ const inter = Inter({ subsets: ['latin'], variable: '--font-body' });
1901
+ const jakarta = Plus_Jakarta_Sans({ subsets: ['latin'], variable: '--font-display', weight: ['600', '700', '800'] });
1902
+
1903
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
1904
+ return (
1905
+ <html lang="pt-BR" className={`${inter.variable} ${jakarta.variable}`}>
1906
+ <body>{children}</body>
1907
+ </html>
1908
+ );
1909
+ }
1910
+ ```
1911
+
1912
+ **Vite (manual):**
1913
+ ```html
1914
+ <!-- index.html -->
1915
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
1916
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
1917
+ <link
1918
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Plus+Jakarta+Sans:wght@600;700;800&display=swap"
1919
+ rel="stylesheet"
1920
+ />
1921
+ ```
1922
+
1923
+ ### Static Export
1924
+
1925
+ **Next.js:**
1926
+ ```ts
1927
+ // next.config.ts
1928
+ const config = { output: 'export' };
1929
+ export default config;
1930
+ ```
1931
+
1932
+ **Vite:** Runs `vite build` by default — output in `dist/`.
1933
+
1934
+ ---
1935
+
1936
+ ## Responsive Strategy
1937
+
1938
+ ### Mobile-First Approach
1939
+
1940
+ All base styles target mobile. Use `min-width` media queries for larger screens:
1941
+
1942
+ ```css
1943
+ /* Base: mobile */
1944
+ .grid {
1945
+ display: grid;
1946
+ grid-template-columns: 1fr;
1947
+ gap: var(--space-6);
1948
+ }
1949
+
1950
+ /* Tablet */
1951
+ @media (min-width: 768px) {
1952
+ .grid { grid-template-columns: 1fr 1fr; gap: var(--space-8); }
1953
+ }
1954
+
1955
+ /* Desktop */
1956
+ @media (min-width: 1024px) {
1957
+ .grid { grid-template-columns: repeat(3, 1fr); gap: var(--space-12); }
1958
+ }
1959
+ ```
1960
+
1961
+ ### Container Component Pattern
1962
+ ```tsx
1963
+ // Already defined above — use <Container> and <Container narrow> everywhere
1964
+ // Never set max-width or horizontal padding on individual sections
1965
+ ```
1966
+
1967
+ ### Responsive Component Example
1968
+ ```tsx
1969
+ import { useIsMobile } from '@/hooks/useMediaQuery';
1970
+
1971
+ function TestimonialsSection({ testimonials }: { testimonials: Testimonial[] }) {
1972
+ const isMobile = useIsMobile();
1973
+
1974
+ return (
1975
+ <Section bg="default">
1976
+ <Container>
1977
+ <div className={styles.grid}>
1978
+ {testimonials.slice(0, isMobile ? 2 : 6).map((t) => (
1979
+ <TestimonialCard key={t.author} {...t} />
1980
+ ))}
1981
+ </div>
1982
+ </Container>
1983
+ </Section>
1984
+ );
1985
+ }
1986
+ ```
1987
+
1988
+ ### Touch Targets
1989
+ - Minimum 44x44px for all interactive elements on mobile
1990
+ - Use padding rather than fixed dimensions to achieve this
1991
+ - Apply `min-height: 44px; min-width: 44px` on clickable atoms
1992
+
1993
+ ---
1994
+
1995
+ ## SEO
1996
+
1997
+ ### Meta Tags
1998
+
1999
+ **Next.js (app router):**
2000
+ ```tsx
2001
+ // app/layout.tsx
2002
+ import type { Metadata } from 'next';
2003
+
2004
+ export const metadata: Metadata = {
2005
+ title: 'Product Name — Tagline',
2006
+ description: 'Clear value proposition in 150-160 characters.',
2007
+ openGraph: {
2008
+ title: 'Product Name — Tagline',
2009
+ description: 'Clear value proposition.',
2010
+ url: 'https://example.com',
2011
+ siteName: 'Product Name',
2012
+ images: [{ url: '/og-image.png', width: 1200, height: 630, alt: 'Product preview' }],
2013
+ type: 'website',
2014
+ locale: 'pt_BR',
2015
+ },
2016
+ twitter: {
2017
+ card: 'summary_large_image',
2018
+ title: 'Product Name — Tagline',
2019
+ description: 'Clear value proposition.',
2020
+ images: ['/og-image.png'],
2021
+ },
2022
+ robots: { index: true, follow: true },
2023
+ alternates: { canonical: 'https://example.com' },
2024
+ };
2025
+ ```
2026
+
2027
+ **Vite + react-helmet-async:**
2028
+ ```tsx
2029
+ import { Helmet } from 'react-helmet-async';
2030
+
2031
+ function SEO({ title, description, url, image }: SEOProps) {
2032
+ return (
2033
+ <Helmet>
2034
+ <title>{title}</title>
2035
+ <meta name="description" content={description} />
2036
+ <link rel="canonical" href={url} />
2037
+ <meta property="og:title" content={title} />
2038
+ <meta property="og:description" content={description} />
2039
+ <meta property="og:url" content={url} />
2040
+ <meta property="og:image" content={image} />
2041
+ <meta property="og:type" content="website" />
2042
+ <meta name="twitter:card" content="summary_large_image" />
2043
+ <meta name="twitter:title" content={title} />
2044
+ <meta name="twitter:description" content={description} />
2045
+ <meta name="twitter:image" content={image} />
2046
+ </Helmet>
2047
+ );
2048
+ }
2049
+ ```
2050
+
2051
+ ### Structured Data (JSON-LD)
2052
+ ```tsx
2053
+ // components/ui/JsonLd.tsx
2054
+ interface JsonLdProps {
2055
+ data: Record<string, unknown>;
2056
+ }
2057
+
2058
+ export default function JsonLd({ data }: JsonLdProps) {
2059
+ return (
2060
+ <script
2061
+ type="application/ld+json"
2062
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }}
2063
+ />
2064
+ );
2065
+ }
2066
+
2067
+ // Usage in page:
2068
+ <JsonLd data={{
2069
+ '@context': 'https://schema.org',
2070
+ '@type': 'SoftwareApplication',
2071
+ name: 'Product Name',
2072
+ description: 'Product description.',
2073
+ url: 'https://example.com',
2074
+ applicationCategory: 'BusinessApplication',
2075
+ offers: {
2076
+ '@type': 'Offer',
2077
+ price: '29',
2078
+ priceCurrency: 'USD',
2079
+ },
2080
+ }} />
2081
+ ```
2082
+
2083
+ ### Semantic HTML in JSX
2084
+ - Use `<header>`, `<main>`, `<section>`, `<article>`, `<footer>` in organisms
2085
+ - Single `<h1>` per page (in HeroSection)
2086
+ - Sequential heading hierarchy: h1 -> h2 -> h3
2087
+ - Use `<nav>` with `aria-label` for navigation blocks
2088
+ - Use `<blockquote>` for testimonials
2089
+
2090
+ ---
2091
+
2092
+ ## Full Page Assembly Example
2093
+
2094
+ ```tsx
2095
+ // app/page.tsx (Next.js) or src/App.tsx (Vite)
2096
+ import Navbar from '@/components/organisms/Navbar';
2097
+ import HeroSection from '@/components/organisms/HeroSection';
2098
+ import FeaturesGrid from '@/components/organisms/FeaturesGrid';
2099
+ import FAQAccordion from '@/components/organisms/FAQAccordion';
2100
+ import CTASection from '@/components/organisms/CTASection';
2101
+ import Footer from '@/components/organisms/Footer';
2102
+ import JsonLd from '@/components/ui/JsonLd';
2103
+
2104
+ const NAV_LINKS = [
2105
+ { label: 'Features', href: '#features' },
2106
+ { label: 'Pricing', href: '#pricing' },
2107
+ { label: 'FAQ', href: '#faq' },
2108
+ ];
2109
+
2110
+ export default function LandingPage() {
2111
+ return (
2112
+ <>
2113
+ <JsonLd data={{ '@context': 'https://schema.org', '@type': 'WebSite', name: 'Product' }} />
2114
+
2115
+ <a href="#main-content" className="sr-only focus:not-sr-only">
2116
+ Skip to content
2117
+ </a>
2118
+
2119
+ <Navbar
2120
+ logo={<span>Product</span>}
2121
+ links={NAV_LINKS}
2122
+ ctaLabel="Get Started"
2123
+ ctaHref="#pricing"
2124
+ />
2125
+
2126
+ <main id="main-content">
2127
+ <HeroSection
2128
+ badge="Now in Beta"
2129
+ title="The headline that hooks visitors"
2130
+ subtitle="A clear explanation of the value you deliver, in one or two sentences."
2131
+ primaryCta="Start Free Trial"
2132
+ primaryHref="/signup"
2133
+ secondaryCta="Watch Demo"
2134
+ secondaryHref="#demo"
2135
+ />
2136
+
2137
+ <FeaturesGrid
2138
+ badge="Features"
2139
+ title="Everything you need"
2140
+ subtitle="Short supporting text that reinforces the value proposition."
2141
+ features={[
2142
+ { icon: <span>&#9889;</span>, title: 'Fast', description: 'Lightning-fast performance.' },
2143
+ { icon: <span>&#128274;</span>, title: 'Secure', description: 'Enterprise-grade security.' },
2144
+ { icon: <span>&#9881;</span>, title: 'Flexible', description: 'Customize everything.' },
2145
+ ]}
2146
+ />
2147
+
2148
+ <FAQAccordion
2149
+ title="Frequently Asked Questions"
2150
+ items={[
2151
+ { question: 'How does it work?', answer: 'Clear, concise answer.' },
2152
+ { question: 'What does it cost?', answer: 'Transparent pricing details.' },
2153
+ ]}
2154
+ />
2155
+
2156
+ <CTASection
2157
+ title="Ready to get started?"
2158
+ subtitle="Join thousands of teams already using Product."
2159
+ primaryLabel="Start Free"
2160
+ primaryHref="/signup"
2161
+ secondaryLabel="Talk to Sales"
2162
+ secondaryHref="/contact"
2163
+ />
2164
+ </main>
2165
+
2166
+ <Footer
2167
+ logo={<span>Product</span>}
2168
+ columns={[
2169
+ { title: 'Product', links: [{ label: 'Features', href: '#features' }, { label: 'Pricing', href: '#pricing' }] },
2170
+ { title: 'Company', links: [{ label: 'About', href: '/about' }, { label: 'Blog', href: '/blog' }] },
2171
+ ]}
2172
+ copyright={`\u00A9 ${new Date().getFullYear()} Product. All rights reserved.`}
2173
+ />
2174
+ </>
2175
+ );
2176
+ }
2177
+ ```
2178
+
2179
+ ### Section Rhythm
2180
+
2181
+ Follow the same alternating density pattern as the HTML version:
2182
+
2183
+ ```
2184
+ [HERO — dark background, dense, above-the-fold]
2185
+ [PROBLEM — light (alt), spacious, breathing room]
2186
+ [SOLUTION — default surface, medium density, alternating columns]
2187
+ [FEATURES — alt surface, grid layout]
2188
+ [TESTIMONIALS/PROOF — dark, full-bleed, contrasting]
2189
+ [PRICING — default surface, card grid]
2190
+ [FAQ — alt surface, accordion]
2191
+ [CTA — dark, gradient, dense]
2192
+ [FOOTER — darkest, links grid]
2193
+ ```
2194
+
2195
+ ---
2196
+
2197
+ ## Quality Checklist
2198
+
2199
+ Before finalizing any React landing page:
2200
+
2201
+ ### Design System
2202
+ - [ ] All values use CSS custom properties (zero hardcoded colors, spacing, or font sizes)
2203
+ - [ ] Design tokens match the HTML landing-page best practices exactly
2204
+ - [ ] Typography uses `clamp()` for fluid sizing
2205
+ - [ ] Shadows are multi-layered (min 2 layers)
2206
+ - [ ] Whitespace between sections is generous (80-128px via `--section-py`)
2207
+ - [ ] Max 3 color families used (base + surface + accent)
2208
+
2209
+ ### Components
2210
+ - [ ] Components follow Atomic Design (atoms, molecules, organisms)
2211
+ - [ ] All components are typed with TypeScript interfaces
2212
+ - [ ] `forwardRef` used on interactive atoms (Button, Input)
2213
+ - [ ] Components accept `className` prop for composition
2214
+ - [ ] No hardcoded content in organisms — all data via props
2215
+
2216
+ ### Accessibility
2217
+ - [ ] Semantic HTML: `<header>`, `<nav>`, `<main>`, `<section>`, `<footer>`
2218
+ - [ ] Single `<h1>`, sequential heading hierarchy
2219
+ - [ ] Skip-to-content link present
2220
+ - [ ] Color contrast: 4.5:1 text, 3:1 UI elements
2221
+ - [ ] `focus-visible` on all interactive elements
2222
+ - [ ] `aria-expanded`, `aria-controls` on accordions and toggles
2223
+ - [ ] `aria-label` on navigation landmarks
2224
+ - [ ] Form inputs have associated `<label>` elements
2225
+ - [ ] `alt` text on all images
2226
+ - [ ] `lang` attribute on `<html>`
2227
+
2228
+ ### Animation
2229
+ - [ ] Scroll animations use `IntersectionObserver` (not scroll events)
2230
+ - [ ] Only `opacity` and `transform` are animated
2231
+ - [ ] `prefers-reduced-motion` respected with `@media` query
2232
+ - [ ] Stagger delay capped at ~400ms total
2233
+ - [ ] `will-change` applied to animated elements
2234
+
2235
+ ### Performance
2236
+ - [ ] Above-the-fold content renders without JavaScript (SSR or static)
2237
+ - [ ] Below-the-fold sections use `React.lazy` + `Suspense`
2238
+ - [ ] Images: WebP format, lazy loading, explicit `width`/`height` or `aspect-ratio`
2239
+ - [ ] Fonts: max 2 families, `font-display: swap`, preconnected
2240
+ - [ ] Bundle size: no unnecessary dependencies (prefer CSS over JS animations)
2241
+ - [ ] Lighthouse score > 90 in all categories
2242
+
2243
+ ### SEO
2244
+ - [ ] `<title>` and `<meta description>` present
2245
+ - [ ] Open Graph tags (title, description, image, url, type)
2246
+ - [ ] Twitter card meta tags
2247
+ - [ ] Canonical URL set
2248
+ - [ ] Structured data (JSON-LD) for product/organization
2249
+ - [ ] Semantic HTML throughout
2250
+
2251
+ ### Responsive
2252
+ - [ ] Mobile-first CSS (`min-width` media queries)
2253
+ - [ ] Touch targets minimum 44x44px
2254
+ - [ ] Content stacks vertically on mobile
2255
+ - [ ] Non-essential content hidden on mobile
2256
+ - [ ] Tested at 320px, 768px, 1024px, 1280px widths
2257
+
2258
+ ### Final Test
2259
+ - [ ] "Squint test" passes — hierarchy is clear at a glance
2260
+ - [ ] Page loads and is interactive under 3 seconds on 3G
2261
+ - [ ] No layout shifts (CLS < 0.1)
2262
+ - [ ] All links and CTAs work
2263
+ - [ ] Looks correct with and without JavaScript