@pylonsync/create-pylon 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (675) hide show
  1. package/bin/create-pylon.js +30 -7
  2. package/package.json +1 -1
  3. package/templates/agency/.env.example +7 -0
  4. package/templates/agency/README.md +28 -3
  5. package/templates/agency/app/(marketing)/contact-form.tsx +20 -18
  6. package/templates/agency/app/(marketing)/page.tsx +3 -3
  7. package/templates/agency/app/(marketing)/work/page.tsx +3 -3
  8. package/templates/agency/app/auth-form.tsx +4 -4
  9. package/templates/agency/app/dashboard/dashboard-client.tsx +110 -64
  10. package/templates/agency/app/dashboard/page.tsx +14 -16
  11. package/templates/agency/app/icon.svg +3 -0
  12. package/templates/agency/app/login/page.tsx +12 -14
  13. package/templates/agency/components/marketing.tsx +12 -5
  14. package/templates/agency/functions/seedStudioBackoffice.ts +42 -11
  15. package/templates/agency/lib/agency.ts +22 -0
  16. package/templates/agency/lib/demo.ts +118 -0
  17. package/templates/agency/lib/site.config.ts +19 -13
  18. package/templates/agency/public/favicon.ico +0 -0
  19. package/templates/agency/tests/agency.test.ts +57 -0
  20. package/templates/ai-chat/.env.example +13 -27
  21. package/templates/ai-chat/README.md +53 -60
  22. package/templates/ai-chat/app/(chat)/c/[id]/page.tsx +19 -0
  23. package/templates/ai-chat/app/(chat)/chat-client.tsx +657 -622
  24. package/templates/ai-chat/app/(chat)/layout.tsx +6 -43
  25. package/templates/ai-chat/app/(chat)/not-found.tsx +6 -10
  26. package/templates/ai-chat/app/(chat)/page.tsx +7 -11
  27. package/templates/ai-chat/app/auth-form.tsx +45 -38
  28. package/templates/ai-chat/app/error.tsx +9 -12
  29. package/templates/ai-chat/app/globals.css +464 -102
  30. package/templates/ai-chat/app/layout.tsx +11 -19
  31. package/templates/ai-chat/app/login/page.tsx +39 -24
  32. package/templates/ai-chat/app.ts +118 -43
  33. package/templates/ai-chat/components/chat/composer.tsx +193 -0
  34. package/templates/ai-chat/components/chat/empty-state.tsx +65 -0
  35. package/templates/ai-chat/components/chat/icons.tsx +198 -0
  36. package/templates/ai-chat/components/chat/markdown.tsx +164 -0
  37. package/templates/ai-chat/components/chat/message.tsx +183 -0
  38. package/templates/ai-chat/components/chat/setup.tsx +71 -0
  39. package/templates/ai-chat/components/chat/sidebar.tsx +288 -0
  40. package/templates/ai-chat/functions/_beginTurn.ts +145 -0
  41. package/templates/ai-chat/functions/_finishTurn.ts +55 -0
  42. package/templates/ai-chat/functions/assistantStatus.ts +13 -0
  43. package/templates/ai-chat/functions/claimGuestHistory.ts +39 -0
  44. package/templates/ai-chat/functions/createConversation.ts +28 -0
  45. package/templates/ai-chat/functions/deleteConversation.ts +15 -20
  46. package/templates/ai-chat/functions/prepareGuestClaim.ts +32 -0
  47. package/templates/ai-chat/functions/renameConversation.ts +19 -0
  48. package/templates/ai-chat/functions/respond.ts +88 -0
  49. package/templates/ai-chat/functions/seedExamples.ts +46 -0
  50. package/templates/ai-chat/functions/stopResponse.ts +30 -0
  51. package/templates/ai-chat/lib/brand-icon.ts +7 -0
  52. package/templates/ai-chat/lib/chat.ts +277 -0
  53. package/templates/ai-chat/lib/claim.ts +15 -0
  54. package/templates/ai-chat/lib/examples.ts +179 -0
  55. package/templates/ai-chat/lib/highlight.ts +107 -0
  56. package/templates/ai-chat/lib/markdown.ts +317 -0
  57. package/templates/ai-chat/lib/provider.ts +38 -0
  58. package/templates/ai-chat/lib/site.config.ts +55 -39
  59. package/templates/ai-chat/tests/chat.test.ts +174 -0
  60. package/templates/ai-chat/tests/components.test.tsx +57 -0
  61. package/templates/ai-chat/tests/markdown.test.ts +88 -0
  62. package/templates/ai-chat/tests/provider.test.ts +32 -0
  63. package/templates/backend/consumer/apps/api/app/layout.tsx +33 -0
  64. package/templates/backend/consumer/apps/api/app/page.tsx +54 -0
  65. package/templates/backend/consumer/apps/api/app.ts +166 -0
  66. package/templates/backend/consumer/apps/api/functions/addComment.ts +24 -0
  67. package/templates/backend/consumer/apps/api/functions/createPost.ts +19 -36
  68. package/templates/backend/consumer/apps/api/functions/deletePost.ts +15 -13
  69. package/templates/backend/consumer/apps/api/functions/myProfile.ts +5 -8
  70. package/templates/backend/consumer/apps/api/functions/seedDemo.ts +95 -0
  71. package/templates/backend/consumer/apps/api/functions/setFollow.ts +36 -0
  72. package/templates/backend/consumer/apps/api/functions/setLike.ts +36 -0
  73. package/templates/backend/consumer/apps/api/functions/upsertProfile.ts +43 -37
  74. package/templates/backend/consumer/apps/api/lib/seed.ts +89 -0
  75. package/templates/backend/consumer/apps/api/lib/social.ts +50 -0
  76. package/templates/backend/consumer/apps/api/package.json +8 -2
  77. package/templates/backend/consumer/apps/api/public/images/posts/arch.jpg +0 -0
  78. package/templates/backend/consumer/apps/api/public/images/posts/coffee.jpg +0 -0
  79. package/templates/backend/consumer/apps/api/public/images/posts/dog.jpg +0 -0
  80. package/templates/backend/consumer/apps/api/public/images/posts/lisbon.jpg +0 -0
  81. package/templates/backend/consumer/apps/api/public/images/posts/ramen.jpg +0 -0
  82. package/templates/backend/consumer/apps/api/public/images/posts/run.jpg +0 -0
  83. package/templates/backend/consumer/apps/api/public/images/posts/sunset.jpg +0 -0
  84. package/templates/backend/consumer/apps/api/tsconfig.json +12 -2
  85. package/templates/chat/README.md +43 -21
  86. package/templates/chat/app/chat-client.tsx +495 -85
  87. package/templates/chat/app/error.tsx +1 -1
  88. package/templates/chat/app/globals.css +170 -54
  89. package/templates/chat/app/layout.tsx +12 -9
  90. package/templates/chat/app/not-found.tsx +4 -2
  91. package/templates/chat/app/page.tsx +9 -19
  92. package/templates/chat/app.ts +96 -17
  93. package/templates/chat/components/chat/avatar.tsx +45 -0
  94. package/templates/chat/components/chat/composer.tsx +110 -0
  95. package/templates/chat/components/chat/message-list.tsx +285 -0
  96. package/templates/chat/components/chat/sidebar.tsx +190 -0
  97. package/templates/chat/functions/joinChat.ts +83 -0
  98. package/templates/chat/functions/renameMember.ts +25 -0
  99. package/templates/chat/lib/chat.ts +188 -0
  100. package/templates/chat/lib/seed.ts +78 -0
  101. package/templates/chat/tests/chat.test.ts +159 -0
  102. package/templates/chat/tests/composer.test.tsx +54 -0
  103. package/templates/consumer/README.md +3 -3
  104. package/templates/consumer/app/globals.css +10 -3
  105. package/templates/consumer/app/icon.svg +1 -0
  106. package/templates/consumer/components/social/avatar.tsx +25 -11
  107. package/templates/consumer/functions/seedFeed.ts +3 -3
  108. package/templates/consumer/lib/seed.ts +43 -85
  109. package/templates/consumer/public/favicon.ico +0 -0
  110. package/templates/consumer/public/images/posts/arch.jpg +0 -0
  111. package/templates/consumer/public/images/posts/coffee.jpg +0 -0
  112. package/templates/consumer/public/images/posts/dog.jpg +0 -0
  113. package/templates/consumer/public/images/posts/lisbon.jpg +0 -0
  114. package/templates/consumer/public/images/posts/ramen.jpg +0 -0
  115. package/templates/consumer/public/images/posts/run.jpg +0 -0
  116. package/templates/consumer/public/images/posts/sunset.jpg +0 -0
  117. package/templates/consumer/tests/seed.test.ts +39 -0
  118. package/templates/creator/.env.example +7 -0
  119. package/templates/creator/README.md +24 -5
  120. package/templates/creator/app/(marketing)/newsletter-signup.tsx +33 -12
  121. package/templates/creator/app/auth-form.tsx +7 -7
  122. package/templates/creator/app/dashboard/dashboard-client.tsx +137 -100
  123. package/templates/creator/app/dashboard/page.tsx +18 -18
  124. package/templates/creator/app/icon.svg +2 -0
  125. package/templates/creator/app/login/page.tsx +14 -14
  126. package/templates/creator/app.ts +4 -4
  127. package/templates/creator/functions/seedDemo.ts +32 -0
  128. package/templates/creator/functions/subscribe.ts +4 -15
  129. package/templates/creator/lib/demo.ts +94 -0
  130. package/templates/creator/lib/site.config.ts +15 -10
  131. package/templates/creator/lib/stats.ts +35 -0
  132. package/templates/creator/public/favicon.ico +0 -0
  133. package/templates/creator/tests/counter.test.ts +92 -0
  134. package/templates/creator/tests/live-count.test.tsx +45 -0
  135. package/templates/crm/README.md +22 -1
  136. package/templates/crm/app/apple-icon.png +0 -0
  137. package/templates/crm/app/companies/companies-view.tsx +32 -14
  138. package/templates/crm/app/companies/page.tsx +2 -1
  139. package/templates/crm/app/contacts/contacts-view.tsx +34 -10
  140. package/templates/crm/app/contacts/page.tsx +2 -1
  141. package/templates/crm/app/deals/[id]/deal-view.tsx +137 -98
  142. package/templates/crm/app/deals/[id]/page.tsx +2 -1
  143. package/templates/crm/app/globals.css +29 -14
  144. package/templates/crm/app/icon.svg +1 -0
  145. package/templates/crm/app/layout.tsx +7 -5
  146. package/templates/crm/app/page.tsx +2 -1
  147. package/templates/crm/app/pipeline-view.tsx +5 -5
  148. package/templates/crm/app/workspace.tsx +29 -17
  149. package/templates/crm/app.ts +5 -5
  150. package/templates/crm/components/activity-timeline.tsx +14 -4
  151. package/templates/crm/components/app-shell.tsx +94 -0
  152. package/templates/crm/components/auth-form.tsx +35 -5
  153. package/templates/crm/components/avatar.tsx +5 -1
  154. package/templates/crm/components/board-skeleton.tsx +5 -5
  155. package/templates/crm/components/brand-mark.tsx +25 -0
  156. package/templates/crm/components/command-palette.tsx +17 -17
  157. package/templates/crm/components/data-table.tsx +97 -36
  158. package/templates/crm/components/deal-card.tsx +23 -27
  159. package/templates/crm/components/filter-tabs.tsx +101 -0
  160. package/templates/crm/components/metrics-bar.tsx +6 -6
  161. package/templates/crm/components/nav.tsx +15 -0
  162. package/templates/crm/components/page-header.tsx +43 -7
  163. package/templates/crm/components/pipeline-board.tsx +16 -10
  164. package/templates/crm/components/require-auth.tsx +24 -26
  165. package/templates/crm/components/sidebar.tsx +54 -49
  166. package/templates/crm/components/stage-badge.tsx +4 -2
  167. package/templates/crm/components/ui/button.tsx +5 -4
  168. package/templates/crm/components/ui/dialog.tsx +1 -1
  169. package/templates/crm/components/ui/input.tsx +1 -1
  170. package/templates/crm/components/ui/select.tsx +1 -1
  171. package/templates/crm/components/ui/textarea.tsx +1 -1
  172. package/templates/crm/functions/seedWorkspace.ts +32 -17
  173. package/templates/crm/lib/brand.ts +9 -0
  174. package/templates/crm/lib/demo-team.ts +51 -0
  175. package/templates/crm/lib/pipeline.ts +37 -0
  176. package/templates/crm/lib/search.ts +4 -3
  177. package/templates/crm/lib/seed.ts +53 -8
  178. package/templates/crm/public/favicon.ico +0 -0
  179. package/templates/crm/tests/components.test.tsx +5 -5
  180. package/templates/crm/tests/demo-team.test.ts +18 -0
  181. package/templates/crm/tests/pipeline.test.ts +59 -3
  182. package/templates/directory/.env.example +7 -0
  183. package/templates/directory/README.md +22 -3
  184. package/templates/directory/app/(marketing)/directory-browse.tsx +30 -12
  185. package/templates/directory/app/(marketing)/page.tsx +6 -4
  186. package/templates/directory/app/auth-form.tsx +4 -4
  187. package/templates/directory/app/dashboard/dashboard-client.tsx +177 -90
  188. package/templates/directory/app/dashboard/page.tsx +16 -14
  189. package/templates/directory/app/icon.svg +4 -0
  190. package/templates/directory/app/login/page.tsx +16 -15
  191. package/templates/directory/functions/seedDemo.ts +43 -0
  192. package/templates/directory/lib/demo.ts +145 -0
  193. package/templates/directory/lib/directory.ts +27 -0
  194. package/templates/directory/lib/site.config.ts +21 -16
  195. package/templates/directory/public/favicon.ico +0 -0
  196. package/templates/directory/tests/directory.test.ts +46 -0
  197. package/templates/expo/consumer/apps/expo/App.tsx +360 -288
  198. package/templates/expo/consumer/apps/expo/app.json +9 -1
  199. package/templates/expo/consumer/apps/expo/package.json +2 -1
  200. package/templates/helpdesk/README.md +21 -0
  201. package/templates/helpdesk/app/apple-icon.png +0 -0
  202. package/templates/helpdesk/app/customers/customers-view.tsx +23 -7
  203. package/templates/helpdesk/app/customers/page.tsx +2 -1
  204. package/templates/helpdesk/app/globals.css +29 -14
  205. package/templates/helpdesk/app/icon.svg +1 -0
  206. package/templates/helpdesk/app/inbox-view.tsx +66 -47
  207. package/templates/helpdesk/app/layout.tsx +7 -5
  208. package/templates/helpdesk/app/page.tsx +4 -7
  209. package/templates/helpdesk/app/tickets/[id]/page.tsx +2 -1
  210. package/templates/helpdesk/app/tickets/[id]/ticket-view.tsx +134 -97
  211. package/templates/helpdesk/app/workspace.tsx +33 -22
  212. package/templates/helpdesk/app.ts +2 -2
  213. package/templates/helpdesk/components/app-shell.tsx +94 -0
  214. package/templates/helpdesk/components/auth-form.tsx +35 -5
  215. package/templates/helpdesk/components/avatar.tsx +5 -1
  216. package/templates/helpdesk/components/brand-mark.tsx +28 -0
  217. package/templates/helpdesk/components/command-palette.tsx +17 -17
  218. package/templates/helpdesk/components/data-table.tsx +97 -36
  219. package/templates/helpdesk/components/filter-tabs.tsx +101 -0
  220. package/templates/helpdesk/components/nav.tsx +14 -0
  221. package/templates/helpdesk/components/page-header.tsx +43 -7
  222. package/templates/helpdesk/components/priority-badge.tsx +49 -6
  223. package/templates/helpdesk/components/require-auth.tsx +24 -26
  224. package/templates/helpdesk/components/sidebar.tsx +54 -48
  225. package/templates/helpdesk/components/sla-indicator.tsx +5 -8
  226. package/templates/helpdesk/components/thread.tsx +7 -7
  227. package/templates/helpdesk/components/ticket-list.tsx +79 -19
  228. package/templates/helpdesk/components/ui/button.tsx +5 -4
  229. package/templates/helpdesk/components/ui/dialog.tsx +1 -1
  230. package/templates/helpdesk/components/ui/input.tsx +1 -1
  231. package/templates/helpdesk/components/ui/select.tsx +1 -1
  232. package/templates/helpdesk/components/ui/textarea.tsx +1 -1
  233. package/templates/helpdesk/functions/seedWorkspace.ts +25 -15
  234. package/templates/helpdesk/lib/brand.ts +9 -0
  235. package/templates/helpdesk/lib/demo-team.ts +51 -0
  236. package/templates/helpdesk/lib/search.ts +4 -3
  237. package/templates/helpdesk/lib/seed.ts +43 -2
  238. package/templates/helpdesk/lib/tickets.ts +68 -20
  239. package/templates/helpdesk/public/favicon.ico +0 -0
  240. package/templates/helpdesk/tests/components.test.tsx +10 -0
  241. package/templates/helpdesk/tests/demo-team.test.ts +18 -0
  242. package/templates/helpdesk/tests/tickets.test.ts +70 -0
  243. package/templates/imessage/.env.example +49 -0
  244. package/templates/imessage/AGENTS.md +153 -0
  245. package/templates/imessage/CLAUDE.md +7 -0
  246. package/templates/imessage/README.md +187 -0
  247. package/templates/imessage/app/console.tsx +382 -0
  248. package/templates/imessage/app/contacts/page.tsx +13 -0
  249. package/templates/imessage/app/error.tsx +16 -0
  250. package/templates/imessage/app/gate.tsx +31 -0
  251. package/templates/imessage/app/globals.css +183 -0
  252. package/templates/imessage/app/layout.tsx +17 -0
  253. package/templates/imessage/app/login/login-form.tsx +115 -0
  254. package/templates/imessage/app/login/page.tsx +30 -0
  255. package/templates/imessage/app/not-found.tsx +13 -0
  256. package/templates/imessage/app/page.tsx +14 -0
  257. package/templates/imessage/app/robots.ts +6 -0
  258. package/templates/imessage/app/setup/page.tsx +13 -0
  259. package/templates/imessage/app/sign-out.tsx +20 -0
  260. package/templates/imessage/app.ts +299 -0
  261. package/templates/imessage/bunfig +4 -0
  262. package/templates/imessage/components/avatar.tsx +46 -0
  263. package/templates/imessage/components/contact-panel.tsx +263 -0
  264. package/templates/imessage/components/contacts-view.tsx +172 -0
  265. package/templates/imessage/components/conversation-list.tsx +129 -0
  266. package/templates/imessage/components/nav-rail.tsx +67 -0
  267. package/templates/imessage/components/setup-view.tsx +408 -0
  268. package/templates/imessage/components/thread.tsx +229 -0
  269. package/templates/imessage/components/ui/badge.tsx +37 -0
  270. package/templates/imessage/components/ui/button.tsx +56 -0
  271. package/templates/imessage/components/ui/card.tsx +90 -0
  272. package/templates/imessage/components/ui/dialog.tsx +110 -0
  273. package/templates/imessage/components/ui/input.tsx +30 -0
  274. package/templates/imessage/components/ui/label.tsx +21 -0
  275. package/templates/imessage/components/ui/select.tsx +39 -0
  276. package/templates/imessage/components/ui/table.tsx +124 -0
  277. package/templates/imessage/components/ui/textarea.tsx +24 -0
  278. package/templates/imessage/components.json +20 -0
  279. package/templates/imessage/functions/answerNow.ts +34 -0
  280. package/templates/imessage/functions/assistant.ts +96 -0
  281. package/templates/imessage/functions/beginTurn.ts +101 -0
  282. package/templates/imessage/functions/cancelReminder.ts +13 -0
  283. package/templates/imessage/functions/claimOutbound.ts +44 -0
  284. package/templates/imessage/functions/clearDemo.ts +18 -0
  285. package/templates/imessage/functions/deleteNote.ts +11 -0
  286. package/templates/imessage/functions/deliverOutbound.ts +55 -0
  287. package/templates/imessage/functions/finishTurn.ts +84 -0
  288. package/templates/imessage/functions/fireReminder.ts +42 -0
  289. package/templates/imessage/functions/ingestInbound.ts +22 -0
  290. package/templates/imessage/functions/ownerAccount.ts +13 -0
  291. package/templates/imessage/functions/processTurn.ts +74 -0
  292. package/templates/imessage/functions/relayApply.ts +102 -0
  293. package/templates/imessage/functions/relaySync.ts +63 -0
  294. package/templates/imessage/functions/releaseOutbound.ts +18 -0
  295. package/templates/imessage/functions/resolveTurn.ts +25 -0
  296. package/templates/imessage/functions/saveContact.ts +27 -0
  297. package/templates/imessage/functions/seedDemo.ts +70 -0
  298. package/templates/imessage/functions/sendOwnerMessage.ts +28 -0
  299. package/templates/imessage/functions/sendblueWebhook.ts +49 -0
  300. package/templates/imessage/functions/setContactStatus.ts +17 -0
  301. package/templates/imessage/functions/settleOutbound.ts +35 -0
  302. package/templates/imessage/functions/setupStatus.ts +33 -0
  303. package/templates/imessage/functions/sweep.ts +58 -0
  304. package/templates/imessage/functions/toolCancelReminder.ts +17 -0
  305. package/templates/imessage/functions/toolForgetNote.ts +15 -0
  306. package/templates/imessage/functions/toolListNotes.ts +16 -0
  307. package/templates/imessage/functions/toolListReminders.ts +17 -0
  308. package/templates/imessage/functions/toolSaveNote.ts +22 -0
  309. package/templates/imessage/functions/toolSetReminder.ts +55 -0
  310. package/templates/imessage/functions/updateSettings.ts +48 -0
  311. package/templates/imessage/gitignore +11 -0
  312. package/templates/imessage/lib/agent-call.ts +251 -0
  313. package/templates/imessage/lib/assistant.ts +74 -0
  314. package/templates/imessage/lib/chunk.ts +135 -0
  315. package/templates/imessage/lib/demo.ts +68 -0
  316. package/templates/imessage/lib/format.ts +170 -0
  317. package/templates/imessage/lib/gate.ts +104 -0
  318. package/templates/imessage/lib/handles.ts +67 -0
  319. package/templates/imessage/lib/provider.ts +38 -0
  320. package/templates/imessage/lib/relay-protocol.ts +95 -0
  321. package/templates/imessage/lib/reminders.ts +41 -0
  322. package/templates/imessage/lib/secrets.ts +59 -0
  323. package/templates/imessage/lib/sendblue.ts +173 -0
  324. package/templates/imessage/lib/store.ts +407 -0
  325. package/templates/imessage/lib/transport.ts +135 -0
  326. package/templates/imessage/lib/utils.ts +10 -0
  327. package/templates/imessage/package.json +40 -0
  328. package/templates/imessage/relay/.env.example +14 -0
  329. package/templates/imessage/relay/applescript.ts +70 -0
  330. package/templates/imessage/relay/chatdb.ts +165 -0
  331. package/templates/imessage/relay/client.ts +41 -0
  332. package/templates/imessage/relay/main.ts +137 -0
  333. package/templates/imessage/relay/state.ts +35 -0
  334. package/templates/imessage/tests/agent-call.test.ts +112 -0
  335. package/templates/imessage/tests/applescript.test.ts +88 -0
  336. package/templates/imessage/tests/chatdb.test.ts +139 -0
  337. package/templates/imessage/tests/chunk.test.ts +61 -0
  338. package/templates/imessage/tests/fake-ctx.ts +88 -0
  339. package/templates/imessage/tests/ingest.test.ts +141 -0
  340. package/templates/imessage/tests/lib.test.ts +99 -0
  341. package/templates/imessage/tests/review-fixes.test.ts +138 -0
  342. package/templates/imessage/tests/secrets.test.ts +36 -0
  343. package/templates/imessage/tests/sendblue.test.ts +143 -0
  344. package/templates/imessage/tests/setup.ts +5 -0
  345. package/templates/imessage/tsconfig.json +18 -0
  346. package/templates/inventory/README.md +21 -0
  347. package/templates/inventory/app/apple-icon.png +0 -0
  348. package/templates/inventory/app/globals.css +29 -14
  349. package/templates/inventory/app/icon.svg +1 -0
  350. package/templates/inventory/app/layout.tsx +7 -5
  351. package/templates/inventory/app/movements/movements-view.tsx +32 -10
  352. package/templates/inventory/app/movements/page.tsx +2 -1
  353. package/templates/inventory/app/page.tsx +4 -6
  354. package/templates/inventory/app/products/[id]/page.tsx +2 -1
  355. package/templates/inventory/app/products/[id]/product-view.tsx +55 -25
  356. package/templates/inventory/app/products-view.tsx +74 -33
  357. package/templates/inventory/app/workspace.tsx +34 -16
  358. package/templates/inventory/app.ts +10 -2
  359. package/templates/inventory/components/app-shell.tsx +94 -0
  360. package/templates/inventory/components/auth-form.tsx +35 -5
  361. package/templates/inventory/components/avatar.tsx +5 -1
  362. package/templates/inventory/components/brand-mark.tsx +25 -0
  363. package/templates/inventory/components/category-tile.tsx +49 -0
  364. package/templates/inventory/components/command-palette.tsx +17 -17
  365. package/templates/inventory/components/data-table.tsx +97 -36
  366. package/templates/inventory/components/filter-tabs.tsx +101 -0
  367. package/templates/inventory/components/nav.tsx +14 -0
  368. package/templates/inventory/components/page-header.tsx +43 -7
  369. package/templates/inventory/components/require-auth.tsx +24 -26
  370. package/templates/inventory/components/sidebar.tsx +54 -48
  371. package/templates/inventory/components/stock-level.tsx +30 -14
  372. package/templates/inventory/components/summary-bar.tsx +8 -15
  373. package/templates/inventory/components/ui/button.tsx +5 -4
  374. package/templates/inventory/components/ui/dialog.tsx +1 -1
  375. package/templates/inventory/components/ui/input.tsx +1 -1
  376. package/templates/inventory/components/ui/select.tsx +1 -1
  377. package/templates/inventory/components/ui/textarea.tsx +1 -1
  378. package/templates/inventory/functions/seedWorkspace.ts +18 -8
  379. package/templates/inventory/lib/brand.ts +9 -0
  380. package/templates/inventory/lib/demo-team.ts +51 -0
  381. package/templates/inventory/lib/search.ts +4 -3
  382. package/templates/inventory/lib/seed.ts +29 -2
  383. package/templates/inventory/lib/stock.ts +32 -0
  384. package/templates/inventory/public/favicon.ico +0 -0
  385. package/templates/inventory/tests/demo-team.test.ts +18 -0
  386. package/templates/inventory/tests/stock.test.ts +29 -0
  387. package/templates/invoices/README.md +16 -0
  388. package/templates/invoices/app/apple-icon.png +0 -0
  389. package/templates/invoices/app/clients/clients-view.tsx +18 -6
  390. package/templates/invoices/app/clients/page.tsx +2 -1
  391. package/templates/invoices/app/globals.css +29 -14
  392. package/templates/invoices/app/icon.svg +1 -0
  393. package/templates/invoices/app/invoices/[id]/invoice-view.tsx +72 -48
  394. package/templates/invoices/app/invoices/[id]/page.tsx +2 -1
  395. package/templates/invoices/app/invoices-view.tsx +81 -32
  396. package/templates/invoices/app/layout.tsx +7 -5
  397. package/templates/invoices/app/page.tsx +4 -6
  398. package/templates/invoices/app/workspace.tsx +26 -16
  399. package/templates/invoices/app.ts +2 -2
  400. package/templates/invoices/components/app-shell.tsx +94 -0
  401. package/templates/invoices/components/auth-form.tsx +35 -5
  402. package/templates/invoices/components/avatar.tsx +5 -1
  403. package/templates/invoices/components/brand-mark.tsx +26 -0
  404. package/templates/invoices/components/command-palette.tsx +17 -17
  405. package/templates/invoices/components/data-table.tsx +97 -36
  406. package/templates/invoices/components/filter-tabs.tsx +101 -0
  407. package/templates/invoices/components/line-items.tsx +19 -14
  408. package/templates/invoices/components/nav.tsx +14 -0
  409. package/templates/invoices/components/page-header.tsx +43 -7
  410. package/templates/invoices/components/require-auth.tsx +24 -26
  411. package/templates/invoices/components/sidebar.tsx +54 -48
  412. package/templates/invoices/components/status-badge.tsx +7 -7
  413. package/templates/invoices/components/summary-bar.tsx +5 -5
  414. package/templates/invoices/components/ui/button.tsx +5 -4
  415. package/templates/invoices/components/ui/dialog.tsx +1 -1
  416. package/templates/invoices/components/ui/input.tsx +1 -1
  417. package/templates/invoices/components/ui/select.tsx +1 -1
  418. package/templates/invoices/components/ui/textarea.tsx +1 -1
  419. package/templates/invoices/functions/recordPayment.ts +3 -0
  420. package/templates/invoices/lib/billing.ts +40 -0
  421. package/templates/invoices/lib/brand.ts +9 -0
  422. package/templates/invoices/lib/search.ts +4 -3
  423. package/templates/invoices/public/favicon.ico +0 -0
  424. package/templates/invoices/tests/billing.test.ts +27 -0
  425. package/templates/ios/consumer/apps/ios/Package.swift +4 -3
  426. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ActivityView.swift +107 -0
  427. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AppModel.swift +171 -0
  428. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AuthView.swift +182 -0
  429. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/CommentsView.swift +136 -0
  430. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ComposeView.swift +152 -0
  431. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ExploreView.swift +117 -0
  432. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/FeedView.swift +175 -159
  433. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/MainTabView.swift +54 -0
  434. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Media.swift +271 -0
  435. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +101 -4
  436. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/PostCard.swift +245 -0
  437. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileEditorView.swift +208 -0
  438. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileView.swift +183 -0
  439. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/RootView.swift +25 -31
  440. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/SocialStore.swift +285 -0
  441. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/StoryViewer.swift +155 -0
  442. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Theme.swift +82 -0
  443. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +4 -47
  444. package/templates/ios/consumer/apps/ios/project.yml +13 -9
  445. package/templates/local-service/.env.example +7 -0
  446. package/templates/local-service/README.md +26 -2
  447. package/templates/local-service/app/(marketing)/booking-widget.tsx +48 -9
  448. package/templates/local-service/app/(marketing)/page.tsx +66 -42
  449. package/templates/local-service/app/auth-form.tsx +8 -8
  450. package/templates/local-service/app/dashboard/dashboard-client.tsx +119 -92
  451. package/templates/local-service/app/dashboard/page.tsx +14 -14
  452. package/templates/local-service/app/icon.svg +2 -0
  453. package/templates/local-service/app/login/page.tsx +15 -15
  454. package/templates/local-service/functions/seedDemo.ts +49 -0
  455. package/templates/local-service/lib/demo.ts +135 -0
  456. package/templates/local-service/lib/hours.ts +44 -0
  457. package/templates/local-service/lib/site.config.ts +8 -3
  458. package/templates/local-service/lib/slots.ts +3 -0
  459. package/templates/local-service/public/favicon.ico +0 -0
  460. package/templates/local-service/tests/booking.test.ts +104 -0
  461. package/templates/marketplace/README.md +23 -15
  462. package/templates/marketplace/app/globals.css +172 -114
  463. package/templates/marketplace/app/layout.tsx +75 -30
  464. package/templates/marketplace/app/listing/[id]/page.tsx +210 -123
  465. package/templates/marketplace/app/me/page.tsx +4 -4
  466. package/templates/marketplace/app/page.tsx +221 -298
  467. package/templates/marketplace/app/sell/page.tsx +8 -11
  468. package/templates/marketplace/app.ts +32 -7
  469. package/templates/marketplace/client/ActivityFeed.tsx +311 -0
  470. package/templates/marketplace/client/AuthNav.tsx +24 -16
  471. package/templates/marketplace/client/ListingCard.tsx +118 -0
  472. package/templates/marketplace/client/LiveGrid.tsx +92 -0
  473. package/templates/marketplace/client/LiveListingStatus.tsx +32 -24
  474. package/templates/marketplace/client/LoginCard.tsx +114 -136
  475. package/templates/marketplace/client/MarketProvider.tsx +3 -5
  476. package/templates/marketplace/client/MyMarket.tsx +371 -185
  477. package/templates/marketplace/client/OfferPanel.tsx +421 -287
  478. package/templates/marketplace/client/SellForm.tsx +271 -192
  479. package/templates/marketplace/client/ThemeToggle.tsx +1 -0
  480. package/templates/marketplace/client/WatchButton.tsx +16 -16
  481. package/templates/marketplace/client/market.ts +61 -14
  482. package/templates/marketplace/functions/seedMarket.ts +143 -36
  483. package/templates/marketplace/functions/seedOffers.ts +101 -0
  484. package/templates/marketplace/lib/catalog.ts +72 -1
  485. package/templates/marketplace/public/images/listings/ceramic-lamp-640.webp +0 -0
  486. package/templates/marketplace/public/images/listings/ceramic-lamp.webp +0 -0
  487. package/templates/marketplace/public/images/listings/duffel-640.webp +0 -0
  488. package/templates/marketplace/public/images/listings/duffel.webp +0 -0
  489. package/templates/marketplace/public/images/listings/film-camera-640.webp +0 -0
  490. package/templates/marketplace/public/images/listings/film-camera.webp +0 -0
  491. package/templates/marketplace/public/images/listings/lounge-chair-640.webp +0 -0
  492. package/templates/marketplace/public/images/listings/lounge-chair.webp +0 -0
  493. package/templates/marketplace/public/images/listings/road-bike-640.webp +0 -0
  494. package/templates/marketplace/public/images/listings/road-bike.webp +0 -0
  495. package/templates/marketplace/public/images/listings/slipper-chair-640.webp +0 -0
  496. package/templates/marketplace/public/images/listings/slipper-chair.webp +0 -0
  497. package/templates/marketplace/public/images/listings/turntable-640.webp +0 -0
  498. package/templates/marketplace/public/images/listings/turntable.webp +0 -0
  499. package/templates/marketplace/public/images/listings/wardrobe-640.webp +0 -0
  500. package/templates/marketplace/public/images/listings/wardrobe.webp +0 -0
  501. package/templates/marketplace/public/images/listings/watch-640.webp +0 -0
  502. package/templates/marketplace/public/images/listings/watch.webp +0 -0
  503. package/templates/marketplace/public/images/listings/weekender-bag-640.webp +0 -0
  504. package/templates/marketplace/public/images/listings/weekender-bag.webp +0 -0
  505. package/templates/marketplace/tests/example.test.ts +64 -2
  506. package/templates/marketplace/tests/listing-card.test.tsx +61 -0
  507. package/templates/projects/README.md +24 -3
  508. package/templates/projects/app/apple-icon.png +0 -0
  509. package/templates/projects/app/clients/clients-view.tsx +20 -6
  510. package/templates/projects/app/clients/page.tsx +2 -1
  511. package/templates/projects/app/globals.css +29 -14
  512. package/templates/projects/app/icon.svg +1 -0
  513. package/templates/projects/app/layout.tsx +7 -5
  514. package/templates/projects/app/page.tsx +3 -5
  515. package/templates/projects/app/projects/[id]/page.tsx +2 -1
  516. package/templates/projects/app/projects/[id]/project-view.tsx +45 -30
  517. package/templates/projects/app/projects-view.tsx +105 -52
  518. package/templates/projects/app/workspace.tsx +41 -20
  519. package/templates/projects/app.ts +2 -2
  520. package/templates/projects/components/app-shell.tsx +94 -0
  521. package/templates/projects/components/auth-form.tsx +35 -5
  522. package/templates/projects/components/avatar.tsx +5 -1
  523. package/templates/projects/components/board-skeleton.tsx +3 -3
  524. package/templates/projects/components/brand-mark.tsx +25 -0
  525. package/templates/projects/components/command-palette.tsx +17 -17
  526. package/templates/projects/components/data-table.tsx +97 -36
  527. package/templates/projects/components/filter-tabs.tsx +101 -0
  528. package/templates/projects/components/meter.tsx +48 -0
  529. package/templates/projects/components/nav.tsx +14 -0
  530. package/templates/projects/components/page-header.tsx +43 -7
  531. package/templates/projects/components/require-auth.tsx +24 -26
  532. package/templates/projects/components/sidebar.tsx +54 -48
  533. package/templates/projects/components/summary-bar.tsx +49 -0
  534. package/templates/projects/components/task-board.tsx +10 -9
  535. package/templates/projects/components/time-dialog.tsx +29 -5
  536. package/templates/projects/components/ui/button.tsx +5 -4
  537. package/templates/projects/components/ui/dialog.tsx +1 -1
  538. package/templates/projects/components/ui/input.tsx +1 -1
  539. package/templates/projects/components/ui/select.tsx +1 -1
  540. package/templates/projects/components/ui/textarea.tsx +1 -1
  541. package/templates/projects/functions/seedWorkspace.ts +26 -12
  542. package/templates/projects/lib/brand.ts +9 -0
  543. package/templates/projects/lib/demo-team.ts +51 -0
  544. package/templates/projects/lib/search.ts +4 -3
  545. package/templates/projects/lib/seed.ts +35 -1
  546. package/templates/projects/lib/work.ts +63 -0
  547. package/templates/projects/public/favicon.ico +0 -0
  548. package/templates/projects/tests/demo-team.test.ts +18 -0
  549. package/templates/projects/tests/work.test.ts +65 -0
  550. package/templates/restaurant/.env.example +7 -0
  551. package/templates/restaurant/README.md +29 -3
  552. package/templates/restaurant/app/(marketing)/layout.tsx +4 -2
  553. package/templates/restaurant/app/(marketing)/page.tsx +47 -23
  554. package/templates/restaurant/app/(marketing)/reservation-widget.tsx +37 -7
  555. package/templates/restaurant/app/auth-form.tsx +8 -8
  556. package/templates/restaurant/app/dashboard/dashboard-client.tsx +241 -109
  557. package/templates/restaurant/app/dashboard/page.tsx +14 -14
  558. package/templates/restaurant/app/icon.svg +4 -0
  559. package/templates/restaurant/app/login/page.tsx +15 -15
  560. package/templates/restaurant/functions/seedDemo.ts +44 -0
  561. package/templates/restaurant/lib/demo.ts +144 -0
  562. package/templates/restaurant/lib/hours.ts +45 -0
  563. package/templates/restaurant/lib/site.config.ts +3 -3
  564. package/templates/restaurant/public/favicon.ico +0 -0
  565. package/templates/restaurant/tests/reservations.test.ts +67 -0
  566. package/templates/saas/README.md +54 -35
  567. package/templates/saas/app/(auth)/auth-form.tsx +25 -23
  568. package/templates/saas/app/(auth)/forgot-password/page.tsx +1 -1
  569. package/templates/saas/app/(auth)/layout.tsx +27 -33
  570. package/templates/saas/app/(auth)/login/page.tsx +1 -1
  571. package/templates/saas/app/(auth)/reset-password/page.tsx +1 -1
  572. package/templates/saas/app/(auth)/signup/page.tsx +1 -1
  573. package/templates/saas/app/(marketing)/layout.tsx +127 -253
  574. package/templates/saas/app/(marketing)/page.tsx +133 -372
  575. package/templates/saas/app/(marketing)/pricing/page.tsx +21 -19
  576. package/templates/saas/app/(marketing)/products/[slug]/page.tsx +40 -95
  577. package/templates/saas/app/dashboard/board-client.tsx +416 -0
  578. package/templates/saas/app/dashboard/dashboard-client.tsx +30 -615
  579. package/templates/saas/app/dashboard/layout.tsx +27 -13
  580. package/templates/saas/app/dashboard/overview-client.tsx +332 -0
  581. package/templates/saas/app/dashboard/page.tsx +29 -35
  582. package/templates/saas/app/dashboard/projects/[id]/page.tsx +29 -0
  583. package/templates/saas/app/dashboard/projects/page.tsx +11 -7
  584. package/templates/saas/app/dashboard/projects-client.tsx +398 -0
  585. package/templates/saas/app/globals.css +5 -4
  586. package/templates/saas/app/icon.svg +1 -0
  587. package/templates/saas/app/onboarding/onboarding-client.tsx +93 -26
  588. package/templates/saas/app/onboarding/page.tsx +6 -2
  589. package/templates/saas/app.ts +68 -13
  590. package/templates/saas/components/dashboard-shell.tsx +212 -132
  591. package/templates/saas/components/marketing.tsx +137 -274
  592. package/templates/saas/components/pricing-table.tsx +12 -11
  593. package/templates/saas/components/setup-checklist.tsx +31 -29
  594. package/templates/saas/components/task-ui.tsx +199 -0
  595. package/templates/saas/functions/createProject.ts +7 -13
  596. package/templates/saas/functions/createTask.ts +67 -0
  597. package/templates/saas/functions/deleteProject.ts +17 -0
  598. package/templates/saas/functions/deleteTask.ts +14 -0
  599. package/templates/saas/functions/seedWorkspace.ts +46 -0
  600. package/templates/saas/functions/setProjectStatus.ts +41 -0
  601. package/templates/saas/functions/updateProfile.ts +13 -0
  602. package/templates/saas/functions/updateProject.ts +4 -5
  603. package/templates/saas/functions/updateTask.ts +88 -0
  604. package/templates/saas/lib/members.ts +53 -0
  605. package/templates/saas/lib/names.ts +53 -0
  606. package/templates/saas/lib/plans.ts +11 -0
  607. package/templates/saas/lib/sample-workspace.ts +117 -0
  608. package/templates/saas/lib/site.config.ts +200 -463
  609. package/templates/saas/lib/tasks.ts +101 -0
  610. package/templates/saas/lib/use-live.ts +25 -0
  611. package/templates/saas/public/favicon.ico +0 -0
  612. package/templates/saas/public/screenshots/board.png +0 -0
  613. package/templates/saas/public/screenshots/members.png +0 -0
  614. package/templates/saas/public/screenshots/overview.png +0 -0
  615. package/templates/saas/public/screenshots/projects.png +0 -0
  616. package/templates/saas/scripts/capture-screenshots.mjs +156 -0
  617. package/templates/saas/tests/names.test.ts +35 -0
  618. package/templates/saas/tests/plans.test.ts +12 -0
  619. package/templates/saas/tests/sample-workspace.test.ts +46 -0
  620. package/templates/saas/tests/tasks.test.ts +81 -0
  621. package/templates/shop/README.md +25 -13
  622. package/templates/shop/app/(marketing)/layout.tsx +94 -67
  623. package/templates/shop/app/(marketing)/not-found.tsx +13 -12
  624. package/templates/shop/app/(marketing)/page.tsx +120 -59
  625. package/templates/shop/app/(marketing)/shop-client.tsx +364 -237
  626. package/templates/shop/app/(marketing)/success/page.tsx +75 -22
  627. package/templates/shop/app/globals.css +94 -1
  628. package/templates/shop/app/layout.tsx +4 -4
  629. package/templates/shop/app/login/page.tsx +4 -4
  630. package/templates/shop/app.ts +18 -6
  631. package/templates/shop/components/cart-button.tsx +33 -0
  632. package/templates/shop/components/marketing.tsx +17 -87
  633. package/templates/shop/components/shelf-count.tsx +36 -0
  634. package/templates/shop/functions/checkout.ts +17 -1
  635. package/templates/shop/lib/cart-store.ts +57 -0
  636. package/templates/shop/lib/shelf-store.ts +35 -0
  637. package/templates/shop/lib/shop.ts +27 -0
  638. package/templates/shop/lib/site.config.ts +108 -98
  639. package/templates/shop/public/images/hero.jpg +0 -0
  640. package/templates/shop/public/images/products/cedar-smoke.jpg +0 -0
  641. package/templates/shop/public/images/products/fig-leaf.jpg +0 -0
  642. package/templates/shop/public/images/products/linen.jpg +0 -0
  643. package/templates/shop/public/images/products/sea-salt.jpg +0 -0
  644. package/templates/shop/public/images/products/tobacco-oak.jpg +0 -0
  645. package/templates/shop/tests/shop.test.ts +64 -0
  646. package/templates/waitlist/.env.example +7 -0
  647. package/templates/waitlist/README.md +29 -13
  648. package/templates/waitlist/app/(marketing)/page.tsx +9 -7
  649. package/templates/waitlist/app/(marketing)/waitlist-hero.tsx +64 -31
  650. package/templates/waitlist/app/auth-form.tsx +8 -8
  651. package/templates/waitlist/app/dashboard/dashboard-client.tsx +132 -95
  652. package/templates/waitlist/app/dashboard/page.tsx +17 -16
  653. package/templates/waitlist/app/icon.svg +2 -0
  654. package/templates/waitlist/app/login/page.tsx +14 -14
  655. package/templates/waitlist/app.ts +6 -6
  656. package/templates/waitlist/functions/joinWaitlist.ts +6 -18
  657. package/templates/waitlist/functions/seedDemo.ts +32 -0
  658. package/templates/waitlist/lib/demo.ts +94 -0
  659. package/templates/waitlist/lib/launch.ts +19 -0
  660. package/templates/waitlist/lib/site.config.ts +14 -9
  661. package/templates/waitlist/lib/stats.ts +35 -0
  662. package/templates/waitlist/public/favicon.ico +0 -0
  663. package/templates/waitlist/tests/counter.test.ts +107 -0
  664. package/templates/waitlist/tests/live-count.test.tsx +48 -0
  665. package/templates/ai-chat/tests/example.test.ts +0 -12
  666. package/templates/backend/consumer/apps/api/functions/feed.ts +0 -57
  667. package/templates/backend/consumer/apps/api/functions/profilePosts.ts +0 -17
  668. package/templates/backend/consumer/apps/api/functions/toggleLike.ts +0 -48
  669. package/templates/backend/consumer/apps/api/schema.ts +0 -143
  670. package/templates/chat/functions/_keep.ts +0 -13
  671. package/templates/chat/tests/example.test.ts +0 -12
  672. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +0 -68
  673. package/templates/marketplace/client/LiveTicker.tsx +0 -116
  674. package/templates/marketplace/client/ScrollToListingsLink.tsx +0 -29
  675. package/templates/marketplace/public/images/marketplace-hero.webp +0 -0
@@ -0,0 +1,44 @@
1
+ // Weekly hours as display rows, from `siteConfig.booking.hours` (the same
2
+ // hours the booking grid uses), so the location block cannot disagree with the
3
+ // grid. Consecutive days with the same hours share a row: "Tue–Wed 9 AM–6 PM".
4
+
5
+ import type { DayHours } from "./site.config";
6
+
7
+ const DAY = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
8
+ // Rows run Monday to Sunday, the way a shop door lists them.
9
+ const ORDER = [1, 2, 3, 4, 5, 6, 0];
10
+
11
+ export interface HoursRow {
12
+ days: string;
13
+ time: string; // "9 AM–6 PM" or "Closed"
14
+ closed: boolean;
15
+ }
16
+
17
+ /** "09:00" → "9 AM", "18:30" → "6:30 PM". */
18
+ export function clock(hhmm: string): string {
19
+ const [h, m] = hhmm.split(":").map(Number);
20
+ const suffix = h >= 12 ? "PM" : "AM";
21
+ const h12 = h % 12 === 0 ? 12 : h % 12;
22
+ return m ? `${h12}:${String(m).padStart(2, "0")} ${suffix}` : `${h12} ${suffix}`;
23
+ }
24
+
25
+ export function hoursRows(hours: Record<number, DayHours>): HoursRow[] {
26
+ const rows: { first: number; last: number; time: string }[] = [];
27
+ for (const d of ORDER) {
28
+ const h = hours[d];
29
+ const time = h ? `${clock(h.open)}–${clock(h.close)}` : "Closed";
30
+ const prev = rows[rows.length - 1];
31
+ if (prev && prev.time === time) prev.last = d;
32
+ else rows.push({ first: d, last: d, time });
33
+ }
34
+ return rows.map((r) => ({
35
+ days: r.first === r.last ? DAY[r.first] : `${DAY[r.first]}–${DAY[r.last]}`,
36
+ time: r.time,
37
+ closed: r.time === "Closed",
38
+ }));
39
+ }
40
+
41
+ /** A Google Maps directions link for a street address. Needs no API key. */
42
+ export function directionsUrl(address: string): string {
43
+ return `https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent(address)}`;
44
+ }
@@ -30,6 +30,7 @@ export type ServiceItem = {
30
30
  name: string;
31
31
  durationMin: number; // drives the booking slot length
32
32
  price: string; // display only, e.g. "$35"
33
+ priceUsd: number; // the same price as a number; the dashboard sums it
33
34
  description?: string;
34
35
  };
35
36
 
@@ -65,8 +66,9 @@ export type LocalServiceConfig = BaseConfig & {
65
66
  location: {
66
67
  headline: string;
67
68
  address: string;
69
+ // A Google Maps embed URL (Share → Embed a map → the iframe's src). When
70
+ // empty, the page shows the weekly hours board beside the address.
68
71
  mapEmbedUrl?: string;
69
- hoursText: string;
70
72
  phone: string;
71
73
  email: string;
72
74
  };
@@ -108,7 +110,7 @@ export const siteConfig: LocalServiceConfig = {
108
110
  "A neighborhood barbershop on Lower Greenville since 2014. Walk in or book a chair online. The calendar updates as chairs fill.",
109
111
  ctaLabel: "Book a chair",
110
112
  quickFacts: {
111
- hours: "Tue–Sat, 9–6",
113
+ hours: "Tue–Sat from 9 AM",
112
114
  area: "Lower Greenville, Dallas",
113
115
  phone: "(214) 555-0148",
114
116
  },
@@ -122,6 +124,7 @@ export const siteConfig: LocalServiceConfig = {
122
124
  name: "Haircut",
123
125
  durationMin: 45,
124
126
  price: "$35",
127
+ priceUsd: 35,
125
128
  description: "Consultation, cut, and a clean finish. The classic.",
126
129
  },
127
130
  {
@@ -129,6 +132,7 @@ export const siteConfig: LocalServiceConfig = {
129
132
  name: "Beard trim",
130
133
  durationMin: 20,
131
134
  price: "$18",
135
+ priceUsd: 18,
132
136
  description: "Shape-up, line work, and hot-towel finish.",
133
137
  },
134
138
  {
@@ -136,6 +140,7 @@ export const siteConfig: LocalServiceConfig = {
136
140
  name: "Cut + beard",
137
141
  durationMin: 60,
138
142
  price: "$48",
143
+ priceUsd: 48,
139
144
  description: "The full sit-down. Haircut and beard, start to finish.",
140
145
  },
141
146
  {
@@ -143,6 +148,7 @@ export const siteConfig: LocalServiceConfig = {
143
148
  name: "Kids' cut",
144
149
  durationMin: 30,
145
150
  price: "$22",
151
+ priceUsd: 22,
146
152
  description: "For the under-12s. Patient barbers, no rush.",
147
153
  },
148
154
  ],
@@ -197,7 +203,6 @@ export const siteConfig: LocalServiceConfig = {
197
203
  headline: "Find us",
198
204
  address: "1845 Greenville Ave, Dallas, TX 75206",
199
205
  mapEmbedUrl: "",
200
- hoursText: "Tue–Wed 9–6 · Thu–Fri 9–7 · Sat 9–4 · Sun–Mon closed",
201
206
  phone: "(214) 555-0148",
202
207
  email: "hello@northgatebarbers.example",
203
208
  },
@@ -21,6 +21,8 @@ export interface Slot {
21
21
  endsAt: string;
22
22
  /** False when the slot is in the past (within lead time) or overlaps a booking. */
23
23
  available: boolean;
24
+ /** Why it is unavailable: "past" (too soon to book) or "taken" (booked). */
25
+ state: "open" | "past" | "taken";
24
26
  }
25
27
 
26
28
  /** Half-open interval overlap: [aStart,aEnd) intersects [bStart,bEnd). */
@@ -75,6 +77,7 @@ export function slotsForDay(opts: {
75
77
  startsAt: new Date(s).toISOString(),
76
78
  endsAt: new Date(e).toISOString(),
77
79
  available: !past && !taken,
80
+ state: past ? "past" : taken ? "taken" : "open",
78
81
  });
79
82
  }
80
83
  return slots;
@@ -0,0 +1,104 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { demoBookings, demoDataEnabled } from "../lib/demo";
3
+ import { clock, directionsUrl, hoursRows } from "../lib/hours";
4
+ import { rangesOverlap, slotsForDay, weekdayOf } from "../lib/slots";
5
+ import { siteConfig } from "../lib/site.config";
6
+
7
+ describe("slotsForDay state", () => {
8
+ const base = {
9
+ dayISODate: "2026-09-29",
10
+ open: "09:00",
11
+ close: "11:00",
12
+ slotMinutes: 30,
13
+ durationMin: 30,
14
+ leadTimeHours: 0,
15
+ };
16
+ test("marks booked times taken and early times past", () => {
17
+ const nine = new Date(2026, 8, 29, 9, 0).getTime();
18
+ const slots = slotsForDay({
19
+ ...base,
20
+ nowMs: nine + 31 * 60_000,
21
+ busy: [
22
+ {
23
+ startsAt: new Date(nine + 60 * 60_000).toISOString(),
24
+ endsAt: new Date(nine + 90 * 60_000).toISOString(),
25
+ },
26
+ ],
27
+ });
28
+ expect(slots.map((s) => s.state)).toEqual(["past", "past", "taken", "open"]);
29
+ expect(slots.filter((s) => s.available)).toHaveLength(1);
30
+ });
31
+ });
32
+
33
+ describe("hoursRows", () => {
34
+ test("groups days with the same hours and lists closed days", () => {
35
+ expect(hoursRows(siteConfig.booking.hours)).toEqual([
36
+ { days: "Mon", time: "Closed", closed: true },
37
+ { days: "Tue–Wed", time: "9 AM–6 PM", closed: false },
38
+ { days: "Thu–Fri", time: "9 AM–7 PM", closed: false },
39
+ { days: "Sat", time: "9 AM–4 PM", closed: false },
40
+ { days: "Sun", time: "Closed", closed: true },
41
+ ]);
42
+ });
43
+ test("clock formats noon, midnight, and half hours", () => {
44
+ expect(clock("12:00")).toBe("12 PM");
45
+ expect(clock("00:00")).toBe("12 AM");
46
+ expect(clock("17:30")).toBe("5:30 PM");
47
+ });
48
+ test("directions link encodes the address", () => {
49
+ expect(directionsUrl("1845 Greenville Ave, Dallas")).toBe(
50
+ "https://www.google.com/maps/dir/?api=1&destination=1845%20Greenville%20Ave%2C%20Dallas",
51
+ );
52
+ });
53
+ });
54
+
55
+ describe("demo data", () => {
56
+ test("only under `pylon dev` unless PYLON_DEMO_DATA says otherwise", () => {
57
+ expect(demoDataEnabled({ PYLON_DEV_MODE: "1", PYLON_DEV_WATCH_DIR: "/src/app" })).toBe(true);
58
+ // The Docker base image turns dev mode on by default; without `pylon dev` it must not seed.
59
+ expect(demoDataEnabled({ PYLON_DEV_MODE: "true" })).toBe(false);
60
+ expect(demoDataEnabled({ PYLON_DEV_MODE: "false" })).toBe(false);
61
+ expect(demoDataEnabled({})).toBe(false);
62
+ expect(demoDataEnabled({ PYLON_DEV_MODE: "1", PYLON_DEV_WATCH_DIR: "/src/app", PYLON_DEMO_DATA: "0" })).toBe(false);
63
+ });
64
+
65
+ const now = new Date(2026, 8, 26, 10, 15).getTime(); // a Saturday morning
66
+ const rows = demoBookings(now, siteConfig);
67
+
68
+ test("every booking sits inside opening hours on an open day", () => {
69
+ expect(rows.length).toBeGreaterThan(40);
70
+ for (const b of rows) {
71
+ const start = new Date(b.startsAt);
72
+ const key = `${start.getFullYear()}-${String(start.getMonth() + 1).padStart(2, "0")}-${String(start.getDate()).padStart(2, "0")}`;
73
+ const hours = siteConfig.booking.hours[weekdayOf(key)];
74
+ expect(hours).not.toBeNull();
75
+ const [ch, cm] = hours!.close.split(":").map(Number);
76
+ const close = new Date(start);
77
+ close.setHours(ch, cm, 0, 0);
78
+ expect(Date.parse(b.endsAt)).toBeLessThanOrEqual(close.getTime());
79
+ }
80
+ });
81
+
82
+ test("active bookings never overlap, so the grid and the dashboard agree", () => {
83
+ const active = rows.filter((b) => b.status !== "cancelled");
84
+ for (let i = 0; i < active.length; i++) {
85
+ for (let j = i + 1; j < active.length; j++) {
86
+ const a = active[i];
87
+ const b = active[j];
88
+ expect(
89
+ rangesOverlap(Date.parse(a.startsAt), Date.parse(a.endsAt), Date.parse(b.startsAt), Date.parse(b.endsAt)),
90
+ ).toBe(false);
91
+ }
92
+ }
93
+ });
94
+
95
+ test("past bookings are confirmed; upcoming ones include some to confirm", () => {
96
+ const past = rows.filter((b) => Date.parse(b.endsAt) <= now);
97
+ const future = rows.filter((b) => Date.parse(b.endsAt) > now);
98
+ expect(past.every((b) => b.status === "confirmed")).toBe(true);
99
+ expect(future.some((b) => b.status === "pending")).toBe(true);
100
+ expect(rows.every((b) => Date.parse(b.createdAt) < now)).toBe(true);
101
+ // Phone numbers stay in the 555-0100..0199 range reserved for fiction.
102
+ for (const b of rows) if (b.customerPhone) expect(b.customerPhone).toMatch(/^\(214\) 555-01\d{2}$/);
103
+ });
104
+ });
@@ -1,6 +1,6 @@
1
1
  # __APP_NAME__
2
2
 
3
- A polished, two-sided resale marketplace built with
3
+ A two-sided resale marketplace built with
4
4
  [Pylon](https://pylonsync.com), with server-rendered product discovery and
5
5
  realtime offers.
6
6
 
@@ -14,29 +14,34 @@ tab.
14
14
  __RUN_DEV__
15
15
  ```
16
16
 
17
- Open http://localhost:4321. The grid seeds itself on first load. Open a
18
- second tab, post something from `/sell` in one, and watch it hit the "just
19
- listed" ticker in the other with no refresh. Sign in (the `/sell` form prefills
20
- a demo account) to list, save finds, make offers, and watch your dashboard
21
- update live.
17
+ Open http://localhost:4321. On first boot the app seeds 10 listings with
18
+ photos, a few open offers, and one sale. Open a second tab, post something
19
+ from `/sell` in one, and watch it rise into the grid and the activity feed in
20
+ the other with no refresh. Sign in (the form prefills a demo account) to buy,
21
+ make offers, save listings, and answer the offers waiting on your dashboard.
22
22
 
23
23
  ## How it works
24
24
 
25
25
  - **Server-rendered for SEO + LCP.** The browse grid (`/`) and every listing
26
26
  page (`/listing/:slug`) render on the server with real rows (`serverData` +
27
27
  React 19 `use()`). View source and the products are *in the HTML*.
28
- - **Realtime where it matters.** The "just listed" ticker, the live offers on a
29
- listing, and your `/me` dashboard all ride the sync engine: one `db.useQuery` per
30
- view, no polling. The public surface connects with an anonymous **guest
31
- session** (read-only); writing (list/offer/buy) requires a real sign-in.
28
+ - **Realtime where it matters.** The browse grid (new listings, offer counts,
29
+ sold status), the activity feed, the offers on a listing, and your `/me`
30
+ dashboard all ride the sync engine: one `db.useQuery` per view, no polling.
31
+ The public surface connects with an anonymous **guest session**
32
+ (read-only); writing (list/offer/buy) requires a real sign-in.
32
33
  - **Unspoofable ownership.** `sellerId`/`buyerId` use `field.owner()`, so the
33
34
  framework stamps them from the session and rejects forged values. Listings
34
35
  and offers can be created with a plain optimistic `db.insert` and still can't
35
36
  be spoofed. The heavier logic (accept = mark sold + auto-decline the rest)
36
37
  runs in `functions/respondToOffer.ts`, where it enforces "only the seller".
37
- - **Commerce-ready discovery.** Browse by category, search title, description,
38
- seller, or category, and sort by recency or price. The logic is pure and
38
+ - **Discovery.** Browse by category, search title, description, seller,
39
+ category, or location, and sort by recency or price. The logic is pure and
39
40
  covered by tests in `tests/example.test.ts`.
41
+ - **Demo data.** `functions/seedMarket.ts` holds the listings and
42
+ `functions/seedOffers.ts` the offers. Photos live in
43
+ `public/images/listings/` (a 1120px and a 640px WebP each). Replace both
44
+ with your own catalog.
40
45
 
41
46
  ## Privacy and policies
42
47
 
@@ -68,7 +73,8 @@ and shipping or pickup workflow before accepting real transactions.
68
73
  ## Rebrand it
69
74
 
70
75
  The brand ("Reprise") lives in `app/layout.tsx`; the demo catalog +
71
- seed account are in `functions/seedMarket.ts`. The design tokens are in
76
+ seed accounts are in `functions/seedMarket.ts`, `functions/seedOffers.ts`,
77
+ and `client/market.ts`. The design tokens are in
72
78
  `ui/tokens.css` + `app/globals.css`.
73
79
 
74
80
  ## Layout
@@ -79,8 +85,10 @@ app/page.tsx SSR discovery, search, sort, category facets
79
85
  app/listing/[id]/page.tsx SSR listing detail (+ generateMetadata)
80
86
  app/sell/page.tsx list an item (sign-in gated)
81
87
  app/me/page.tsx your listings, offers, watchlist (live)
82
- functions/buyNow.ts, makeOffer.ts, respondToOffer.ts, seedMarket.ts
83
- client/* the realtime islands (ticker, offers, sell form…)
88
+ functions/buyNow.ts, makeOffer.ts, respondToOffer.ts, seedMarket.ts, seedOffers.ts
89
+ lib/catalog.ts categories, filtering, offer math (pure, tested)
90
+ client/* the realtime islands (grid, activity feed, offers, sell form)
91
+ public/images/listings/ seeded listing photos
84
92
  ```
85
93
 
86
94
  ## Deploy
@@ -8,52 +8,64 @@
8
8
  @source "./";
9
9
  @source "../client";
10
10
  @source "../components";
11
- @source "../../_shared/src";
12
-
13
- @theme {
14
- --font-sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI",
15
- sans-serif;
11
+ @source "../lib";
12
+
13
+ /* Geist and Instrument Serif are declared in app.ts (`fonts: [font(...)]`);
14
+ the runtime defines --font-geist and --font-instrument. */
15
+ @theme inline {
16
+ --font-sans: var(--font-geist, ui-sans-serif), ui-sans-serif, -apple-system,
17
+ BlinkMacSystemFont, "Segoe UI", sans-serif;
18
+ --font-display: var(--font-instrument, "Iowan Old Style"), "Iowan Old Style",
19
+ Georgia, serif;
16
20
  --font-mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
21
+ }
17
22
 
18
- --color-background: oklch(0.975 0.004 85);
19
- --color-foreground: oklch(0.17 0.004 85);
20
- --color-card: oklch(0.995 0.002 85);
21
- --color-card-foreground: oklch(0.17 0.004 85);
22
- --color-popover: oklch(0.995 0.002 85);
23
- --color-popover-foreground: oklch(0.17 0.004 85);
24
- --color-primary: oklch(0.19 0.004 85);
25
- --color-primary-foreground: oklch(0.98 0.002 85);
26
- --color-secondary: oklch(0.94 0.006 85);
27
- --color-secondary-foreground: oklch(0.21 0.004 85);
28
- --color-muted: oklch(0.945 0.006 85);
29
- --color-muted-foreground: oklch(0.48 0.008 85);
30
- --color-accent: oklch(0.93 0.009 85);
31
- --color-accent-foreground: oklch(0.21 0.004 85);
32
- --color-destructive: oklch(0.577 0.245 27.325);
23
+ @theme {
24
+ --color-background: oklch(0.967 0.007 80);
25
+ --color-foreground: oklch(0.2 0.008 70);
26
+ --color-card: oklch(0.993 0.003 80);
27
+ --color-card-foreground: oklch(0.2 0.008 70);
28
+ --color-popover: oklch(0.993 0.003 80);
29
+ --color-popover-foreground: oklch(0.2 0.008 70);
30
+ --color-primary: oklch(0.2 0.008 70);
31
+ --color-primary-foreground: oklch(0.98 0.004 80);
32
+ --color-secondary: oklch(0.93 0.009 80);
33
+ --color-secondary-foreground: oklch(0.22 0.008 70);
34
+ --color-muted: oklch(0.935 0.009 80);
35
+ --color-muted-foreground: oklch(0.5 0.012 70);
36
+ --color-accent: oklch(0.925 0.011 80);
37
+ --color-accent-foreground: oklch(0.22 0.008 70);
38
+ --color-destructive: oklch(0.56 0.2 28);
33
39
  --color-destructive-foreground: oklch(0.985 0 0);
34
- --color-success: oklch(0.62 0.14 153);
35
- --color-success-foreground: oklch(0.38 0.1 153);
36
- --color-warning: oklch(0.72 0.14 75);
37
- --color-warning-foreground: oklch(0.42 0.1 65);
38
- --color-border: oklch(0.89 0.007 85);
39
- --color-input: oklch(0.86 0.008 85);
40
- --color-ring: oklch(0.53 0.015 85);
40
+ --color-success: oklch(0.6 0.12 155);
41
+ --color-success-foreground: oklch(0.4 0.09 155);
42
+ --color-warning: oklch(0.74 0.14 70);
43
+ --color-warning-foreground: oklch(0.46 0.1 60);
44
+ /* Live signal: pending offers, the live dot, "just listed". */
45
+ --color-signal: oklch(0.63 0.19 38);
46
+ --color-signal-foreground: oklch(0.5 0.17 38);
47
+ --color-border: oklch(0.895 0.011 80);
48
+ --color-input: oklch(0.87 0.012 80);
49
+ --color-ring: oklch(0.55 0.02 70);
41
50
 
42
51
  --radius-lg: 1rem;
43
52
  --radius-md: 0.625rem;
44
53
  --radius-sm: 0.5rem;
54
+
55
+ --ease-out-soft: cubic-bezier(0.22, 1, 0.36, 1);
45
56
  }
46
57
 
47
58
  :root {
48
59
  color-scheme: light;
49
60
  --shadow-border:
50
- 0 0 0 1px rgb(0 0 0 / 0.06),
51
- 0 1px 2px -1px rgb(0 0 0 / 0.06),
52
- 0 8px 24px -16px rgb(41 37 33 / 0.18);
61
+ 0 0 0 1px rgb(40 33 24 / 0.07),
62
+ 0 1px 2px -1px rgb(40 33 24 / 0.06),
63
+ 0 10px 28px -18px rgb(40 33 24 / 0.2);
53
64
  --shadow-border-hover:
54
- 0 0 0 1px rgb(0 0 0 / 0.09),
55
- 0 2px 4px -2px rgb(0 0 0 / 0.08),
56
- 0 16px 32px -18px rgb(41 37 33 / 0.24);
65
+ 0 0 0 1px rgb(40 33 24 / 0.1),
66
+ 0 2px 4px -2px rgb(40 33 24 / 0.08),
67
+ 0 18px 36px -20px rgb(40 33 24 / 0.28);
68
+ --shadow-float: 0 1px 2px rgb(40 33 24 / 0.08), 0 8px 20px -12px rgb(40 33 24 / 0.35);
57
69
  }
58
70
 
59
71
  :root[data-theme="light"] {
@@ -62,61 +74,102 @@
62
74
 
63
75
  :root[data-theme="dark"] {
64
76
  color-scheme: dark;
65
- --color-background: oklch(0.15 0.004 85);
66
- --color-foreground: oklch(0.94 0.004 85);
67
- --color-card: oklch(0.19 0.005 85);
68
- --color-card-foreground: oklch(0.94 0.004 85);
69
- --color-popover: oklch(0.19 0.005 85);
70
- --color-popover-foreground: oklch(0.94 0.004 85);
71
- --color-primary: oklch(0.93 0.004 85);
72
- --color-primary-foreground: oklch(0.17 0.004 85);
73
- --color-secondary: oklch(0.24 0.006 85);
74
- --color-secondary-foreground: oklch(0.94 0.004 85);
75
- --color-muted: oklch(0.23 0.006 85);
76
- --color-muted-foreground: oklch(0.7 0.007 85);
77
- --color-accent: oklch(0.25 0.008 85);
78
- --color-accent-foreground: oklch(0.94 0.004 85);
79
- --color-border: oklch(0.3 0.007 85);
80
- --color-input: oklch(0.34 0.008 85);
81
- --color-ring: oklch(0.68 0.01 85);
82
- --color-success-foreground: oklch(0.82 0.12 153);
77
+ --color-background: oklch(0.165 0.006 70);
78
+ --color-foreground: oklch(0.94 0.006 80);
79
+ --color-card: oklch(0.2 0.007 70);
80
+ --color-card-foreground: oklch(0.94 0.006 80);
81
+ --color-popover: oklch(0.2 0.007 70);
82
+ --color-popover-foreground: oklch(0.94 0.006 80);
83
+ --color-primary: oklch(0.94 0.006 80);
84
+ --color-primary-foreground: oklch(0.18 0.006 70);
85
+ --color-secondary: oklch(0.25 0.008 70);
86
+ --color-secondary-foreground: oklch(0.94 0.006 80);
87
+ --color-muted: oklch(0.24 0.008 70);
88
+ --color-muted-foreground: oklch(0.7 0.01 75);
89
+ --color-accent: oklch(0.26 0.009 70);
90
+ --color-accent-foreground: oklch(0.94 0.006 80);
91
+ --color-border: oklch(0.3 0.008 70);
92
+ --color-input: oklch(0.34 0.009 70);
93
+ --color-ring: oklch(0.68 0.012 75);
94
+ --color-success-foreground: oklch(0.8 0.11 155);
83
95
  --color-warning-foreground: oklch(0.84 0.12 75);
96
+ --color-signal: oklch(0.7 0.17 42);
97
+ --color-signal-foreground: oklch(0.78 0.14 45);
84
98
  --shadow-border: 0 0 0 1px rgb(255 255 255 / 0.08);
85
99
  --shadow-border-hover: 0 0 0 1px rgb(255 255 255 / 0.14);
100
+ --shadow-float: 0 0 0 1px rgb(255 255 255 / 0.08), 0 8px 20px -12px rgb(0 0 0 / 0.6);
86
101
  }
87
102
 
88
- html,
89
- body {
90
- height: 100%;
103
+ @media (prefers-color-scheme: dark) {
104
+ :root:not([data-theme]) {
105
+ color-scheme: dark;
106
+ --color-background: oklch(0.165 0.006 70);
107
+ --color-foreground: oklch(0.94 0.006 80);
108
+ --color-card: oklch(0.2 0.007 70);
109
+ --color-card-foreground: oklch(0.94 0.006 80);
110
+ --color-popover: oklch(0.2 0.007 70);
111
+ --color-popover-foreground: oklch(0.94 0.006 80);
112
+ --color-primary: oklch(0.94 0.006 80);
113
+ --color-primary-foreground: oklch(0.18 0.006 70);
114
+ --color-secondary: oklch(0.25 0.008 70);
115
+ --color-secondary-foreground: oklch(0.94 0.006 80);
116
+ --color-muted: oklch(0.24 0.008 70);
117
+ --color-muted-foreground: oklch(0.7 0.01 75);
118
+ --color-accent: oklch(0.26 0.009 70);
119
+ --color-accent-foreground: oklch(0.94 0.006 80);
120
+ --color-border: oklch(0.3 0.008 70);
121
+ --color-input: oklch(0.34 0.009 70);
122
+ --color-ring: oklch(0.68 0.012 75);
123
+ --color-success-foreground: oklch(0.8 0.11 155);
124
+ --color-warning-foreground: oklch(0.84 0.12 75);
125
+ --color-signal: oklch(0.7 0.17 42);
126
+ --color-signal-foreground: oklch(0.78 0.14 45);
127
+ --shadow-border: 0 0 0 1px rgb(255 255 255 / 0.08);
128
+ --shadow-border-hover: 0 0 0 1px rgb(255 255 255 / 0.14);
129
+ --shadow-float: 0 0 0 1px rgb(255 255 255 / 0.08), 0 8px 20px -12px rgb(0 0 0 / 0.6);
130
+ }
91
131
  }
92
132
 
93
- html {
94
- -webkit-tap-highlight-color: rgb(23 23 23 / 0.08);
95
- }
133
+ /* Base element rules sit in the base layer so utilities (text-nowrap,
134
+ truncate) override them. */
135
+ @layer base {
136
+ html,
137
+ body {
138
+ height: 100%;
139
+ }
96
140
 
97
- h1,
98
- h2,
99
- h3 {
100
- scroll-margin-top: 6rem;
101
- text-wrap: balance;
102
- }
141
+ html {
142
+ -webkit-tap-highlight-color: rgb(23 23 23 / 0.08);
143
+ }
103
144
 
104
- button,
105
- a,
106
- input,
107
- select,
108
- textarea {
109
- touch-action: manipulation;
110
- }
145
+ body {
146
+ font-feature-settings: "ss01", "cv11";
147
+ }
111
148
 
112
- :where(a, button, input, select, textarea):focus-visible {
113
- outline: 2px solid var(--color-ring);
114
- outline-offset: 2px;
115
- }
149
+ h1,
150
+ h2,
151
+ h3 {
152
+ scroll-margin-top: 6rem;
153
+ text-wrap: balance;
154
+ }
155
+
156
+ button,
157
+ a,
158
+ input,
159
+ select,
160
+ textarea {
161
+ touch-action: manipulation;
162
+ }
116
163
 
117
- p,
118
- li {
119
- text-wrap: pretty;
164
+ :where(a, button, input, select, textarea):focus-visible {
165
+ outline: 2px solid var(--color-ring);
166
+ outline-offset: 2px;
167
+ }
168
+
169
+ p,
170
+ li {
171
+ text-wrap: pretty;
172
+ }
120
173
  }
121
174
 
122
175
  .hide-scrollbar {
@@ -127,54 +180,59 @@ li {
127
180
  display: none;
128
181
  }
129
182
 
130
- @media (prefers-color-scheme: dark) {
131
- :root:not([data-theme]) {
132
- color-scheme: dark;
133
- --color-background: oklch(0.15 0.004 85);
134
- --color-foreground: oklch(0.94 0.004 85);
135
- --color-card: oklch(0.19 0.005 85);
136
- --color-card-foreground: oklch(0.94 0.004 85);
137
- --color-popover: oklch(0.19 0.005 85);
138
- --color-popover-foreground: oklch(0.94 0.004 85);
139
- --color-primary: oklch(0.93 0.004 85);
140
- --color-primary-foreground: oklch(0.17 0.004 85);
141
- --color-secondary: oklch(0.24 0.006 85);
142
- --color-secondary-foreground: oklch(0.94 0.004 85);
143
- --color-muted: oklch(0.23 0.006 85);
144
- --color-muted-foreground: oklch(0.7 0.007 85);
145
- --color-accent: oklch(0.25 0.008 85);
146
- --color-accent-foreground: oklch(0.94 0.004 85);
147
- --color-border: oklch(0.3 0.007 85);
148
- --color-input: oklch(0.34 0.008 85);
149
- --color-ring: oklch(0.68 0.01 85);
150
- --color-success-foreground: oklch(0.82 0.12 153);
151
- --color-warning-foreground: oklch(0.84 0.12 75);
152
- --shadow-border: 0 0 0 1px rgb(255 255 255 / 0.08);
153
- --shadow-border-hover: 0 0 0 1px rgb(255 255 255 / 0.14);
183
+ /* A row that arrives over the sync connection rises into place. */
184
+ @keyframes market-rise {
185
+ from {
186
+ opacity: 0;
187
+ transform: translateY(10px) scale(0.985);
188
+ }
189
+ to {
190
+ opacity: 1;
191
+ transform: none;
154
192
  }
155
193
  }
194
+ .market-rise {
195
+ animation: market-rise 520ms var(--ease-out-soft) both;
196
+ }
156
197
 
157
- /* Live "just listed" ticker. Items are doubled in the markup, so translating
158
- the track by -50% loops seamlessly. */
159
- @keyframes market-marquee {
198
+ /* A new offer or event gets a short signal-colored wash. */
199
+ @keyframes market-flash {
160
200
  from {
161
- transform: translateX(0);
201
+ background-color: color-mix(in oklch, var(--color-signal) 16%, transparent);
162
202
  }
163
203
  to {
164
- transform: translateX(-50%);
204
+ background-color: transparent;
165
205
  }
166
206
  }
167
- .market-marquee {
168
- animation: market-marquee 28s linear infinite;
207
+ .market-flash {
208
+ animation: market-flash 2.4s var(--ease-out-soft) both;
169
209
  }
170
- .market-marquee:hover {
171
- animation-play-state: paused;
210
+
211
+ .market-rise.market-flash {
212
+ animation:
213
+ market-rise 520ms var(--ease-out-soft) both,
214
+ market-flash 2.4s var(--ease-out-soft) both;
172
215
  }
173
- @media (prefers-reduced-motion: reduce) {
174
- .market-marquee {
175
- animation: none;
216
+
217
+ @keyframes market-ping {
218
+ 0% {
219
+ transform: scale(1);
220
+ opacity: 0.7;
176
221
  }
177
- .animate-pulse {
222
+ 80%,
223
+ 100% {
224
+ transform: scale(2.6);
225
+ opacity: 0;
226
+ }
227
+ }
228
+ .market-ping {
229
+ animation: market-ping 1.8s var(--ease-out-soft) infinite;
230
+ }
231
+
232
+ @media (prefers-reduced-motion: reduce) {
233
+ .market-rise,
234
+ .market-flash,
235
+ .market-ping {
178
236
  animation: none;
179
237
  }
180
238
  }