tanstack_start_ts 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (336) hide show
  1. package/.wrangler/deploy/config.json +1 -0
  2. package/bunfig.toml +6 -0
  3. package/components.json +22 -0
  4. package/config.json +0 -0
  5. package/dist/client/.assetsignore +2 -0
  6. package/dist/client/assets/ProductCard-DbIkJAE-.js +1 -0
  7. package/dist/client/assets/about-AskxOruL.js +1 -0
  8. package/dist/client/assets/admin-BZVcAQM3.js +1 -0
  9. package/dist/client/assets/admin.functions--RdVcuBx.js +1 -0
  10. package/dist/client/assets/admin.login-QgrF_9Fp.js +1 -0
  11. package/dist/client/assets/affiliate-disclosure-BIAsA-HO.js +1 -0
  12. package/dist/client/assets/categories-D0N418mK.js +1 -0
  13. package/dist/client/assets/category._slug-aCaQm14E.js +1 -0
  14. package/dist/client/assets/contact-PhvO-V15.js +1 -0
  15. package/dist/client/assets/faq-BsiHWPM8.js +1 -0
  16. package/dist/client/assets/hero-bg-BP2eVUIX.jpg +0 -0
  17. package/dist/client/assets/index-BU9rnkF3.js +1 -0
  18. package/dist/client/assets/index-BpJWZkva.js +1 -0
  19. package/dist/client/assets/index-vRX-zAyq.js +1 -0
  20. package/dist/client/assets/login-DteE0ZGp.js +1 -0
  21. package/dist/client/assets/logo-pSNfLJQk.png +0 -0
  22. package/dist/client/assets/privacy-B_Pu7040.js +1 -0
  23. package/dist/client/assets/product-links-BkZ41Gv3.js +1 -0
  24. package/dist/client/assets/product._id-BVUysCW-.js +1 -0
  25. package/dist/client/assets/products.functions-cGzRziKO.js +1 -0
  26. package/dist/client/assets/profile-CveRcKq2.js +1 -0
  27. package/dist/client/assets/reset-password-ySEjItX_.js +1 -0
  28. package/dist/client/assets/saved-CHtdQDJF.js +1 -0
  29. package/dist/client/assets/search-CXWfET1y.js +1 -0
  30. package/dist/client/assets/signup-CEx90iuV.js +1 -0
  31. package/dist/client/assets/styles-DrNJG0BO.css +1 -0
  32. package/dist/client/assets/terms-VqJ9kX9b.js +1 -0
  33. package/dist/client/assets/update-password-C-d0ix5e.js +1 -0
  34. package/dist/client/assets/vendor-aria-hidden-DvXkyWUv.js +1 -0
  35. package/dist/client/assets/vendor-class-variance-authority-5VPnzWs2.js +1 -0
  36. package/dist/client/assets/vendor-clsx-B-dksMZM.js +1 -0
  37. package/dist/client/assets/vendor-cookie-es-CS0aJGDi.js +1 -0
  38. package/dist/client/assets/vendor-detect-node-es-l0sNRNKZ.js +1 -0
  39. package/dist/client/assets/vendor-floating-ui-core-BlUy28sp.js +1 -0
  40. package/dist/client/assets/vendor-floating-ui-dom-BxK0hn2R.js +1 -0
  41. package/dist/client/assets/vendor-floating-ui-react-dom-Bas3975S.js +1 -0
  42. package/dist/client/assets/vendor-floating-ui-utils-BfYUAVcw.js +1 -0
  43. package/dist/client/assets/vendor-framer-motion-BMdL-cuX.js +9 -0
  44. package/dist/client/assets/vendor-get-nonce-C-Z93AgS.js +1 -0
  45. package/dist/client/assets/vendor-iceberg-js-tWD4K6Lg.js +1 -0
  46. package/dist/client/assets/vendor-lovable.dev-cloud-auth-js-VuzqtJVg.js +1 -0
  47. package/dist/client/assets/vendor-lucide-react-b5K2fehp.js +1 -0
  48. package/dist/client/assets/vendor-motion-dom-BETJamZt.js +1 -0
  49. package/dist/client/assets/vendor-motion-utils-BuWewJbj.js +1 -0
  50. package/dist/client/assets/vendor-radix-ui-primitive-Dc_FVRD7.js +1 -0
  51. package/dist/client/assets/vendor-radix-ui-react-accordion-C22Rgxe9.js +1 -0
  52. package/dist/client/assets/vendor-radix-ui-react-arrow-DMHj2mKI.js +1 -0
  53. package/dist/client/assets/vendor-radix-ui-react-avatar-CVPBkFXg.js +1 -0
  54. package/dist/client/assets/vendor-radix-ui-react-collapsible-BvM-4sKX.js +1 -0
  55. package/dist/client/assets/vendor-radix-ui-react-collection-D9KtqmHm.js +1 -0
  56. package/dist/client/assets/vendor-radix-ui-react-compose-refs-Cvq0AS8Z.js +1 -0
  57. package/dist/client/assets/vendor-radix-ui-react-context-CAqqn5Nx.js +1 -0
  58. package/dist/client/assets/vendor-radix-ui-react-dialog-DZ01vOLq.js +5 -0
  59. package/dist/client/assets/vendor-radix-ui-react-direction-DxZwNuei.js +1 -0
  60. package/dist/client/assets/vendor-radix-ui-react-dismissable-layer-Dqgrs55Y.js +1 -0
  61. package/dist/client/assets/vendor-radix-ui-react-dropdown-menu-0uzvrqkn.js +1 -0
  62. package/dist/client/assets/vendor-radix-ui-react-focus-guards-DgWoZ-fP.js +1 -0
  63. package/dist/client/assets/vendor-radix-ui-react-focus-scope-BLIu5QaL.js +1 -0
  64. package/dist/client/assets/vendor-radix-ui-react-id-bpga_rLa.js +1 -0
  65. package/dist/client/assets/vendor-radix-ui-react-menu-D0qf2r6_.js +1 -0
  66. package/dist/client/assets/vendor-radix-ui-react-popper-BafIylxU.js +1 -0
  67. package/dist/client/assets/vendor-radix-ui-react-portal-BnAsfNCS.js +1 -0
  68. package/dist/client/assets/vendor-radix-ui-react-presence-C-f3UKQ2.js +1 -0
  69. package/dist/client/assets/vendor-radix-ui-react-primitive-zTHwXNoz.js +1 -0
  70. package/dist/client/assets/vendor-radix-ui-react-roving-focus-jyJB8K2E.js +1 -0
  71. package/dist/client/assets/vendor-radix-ui-react-slot-6LXHJrHl.js +1 -0
  72. package/dist/client/assets/vendor-radix-ui-react-use-callback-ref-E91aPc6s.js +1 -0
  73. package/dist/client/assets/vendor-radix-ui-react-use-controllable-state-Ca3eMtxa.js +1 -0
  74. package/dist/client/assets/vendor-radix-ui-react-use-effect-event-CPeX4A3c.js +1 -0
  75. package/dist/client/assets/vendor-radix-ui-react-use-escape-keydown-7n3YsXFo.js +1 -0
  76. package/dist/client/assets/vendor-radix-ui-react-use-is-hydrated-C1PY1qNv.js +1 -0
  77. package/dist/client/assets/vendor-radix-ui-react-use-layout-effect-B3AcGWPy.js +1 -0
  78. package/dist/client/assets/vendor-radix-ui-react-use-size-CXS04sct.js +1 -0
  79. package/dist/client/assets/vendor-react-dom-BnNs-kzm.js +9 -0
  80. package/dist/client/assets/vendor-react-gJPiVnX5.js +1 -0
  81. package/dist/client/assets/vendor-react-remove-scroll-DHKl-IMP.js +4 -0
  82. package/dist/client/assets/vendor-react-remove-scroll-bar-CSjdInc2.js +38 -0
  83. package/dist/client/assets/vendor-react-style-singleton-BqHpkgXn.js +1 -0
  84. package/dist/client/assets/vendor-scheduler-7OC5HNn7.js +1 -0
  85. package/dist/client/assets/vendor-seroval-B_Fur-nl.js +3 -0
  86. package/dist/client/assets/vendor-seroval-plugins-CBHnPkZJ.js +1 -0
  87. package/dist/client/assets/vendor-sonner-71-LdGG1.js +1 -0
  88. package/dist/client/assets/vendor-supabase-auth-js-DWrN-bIx.js +18 -0
  89. package/dist/client/assets/vendor-supabase-functions-js-uY_V-TxC.js +1 -0
  90. package/dist/client/assets/vendor-supabase-phoenix-BzEf37Ve.js +2 -0
  91. package/dist/client/assets/vendor-supabase-postgrest-js-C4rBWbCx.js +4 -0
  92. package/dist/client/assets/vendor-supabase-realtime-js-D6BlOYKE.js +23 -0
  93. package/dist/client/assets/vendor-supabase-storage-js-BG98L3Zz.js +1 -0
  94. package/dist/client/assets/vendor-supabase-supabase-js-DCCzdwBJ.js +1 -0
  95. package/dist/client/assets/vendor-tailwind-merge-Ct12j0u0.js +1 -0
  96. package/dist/client/assets/vendor-tanstack-history-C617CaxG.js +1 -0
  97. package/dist/client/assets/vendor-tanstack-query-core-7wuJJ5ZL.js +1 -0
  98. package/dist/client/assets/vendor-tanstack-react-query-HImzo8sX.js +1 -0
  99. package/dist/client/assets/vendor-tanstack-react-router-sIZLK-LU.js +1 -0
  100. package/dist/client/assets/vendor-tanstack-react-start-client-GiYCfWmf.js +1 -0
  101. package/dist/client/assets/vendor-tanstack-react-store-EvTi3ahh.js +1 -0
  102. package/dist/client/assets/vendor-tanstack-router-core-Cr7bYUZv.js +1 -0
  103. package/dist/client/assets/vendor-tanstack-start-client-core-C-00BBOu.js +2 -0
  104. package/dist/client/assets/vendor-tanstack-start-fn-stubs-l0sNRNKZ.js +1 -0
  105. package/dist/client/assets/vendor-tanstack-store-BC7mA7pq.js +1 -0
  106. package/dist/client/assets/vendor-tslib-Du-meQkk.js +1 -0
  107. package/dist/client/assets/vendor-use-callback-ref-C_fIAtot.js +1 -0
  108. package/dist/client/assets/vendor-use-sidecar-Bh0DDN6h.js +1 -0
  109. package/dist/client/assets/vendor-use-sync-external-store-ZvKHXaIn.js +1 -0
  110. package/dist/client/assets/vendor-vercel-analytics-DwPM5BWs.js +1 -0
  111. package/dist/client/assets/vendor-zod-By9teAtI.js +1 -0
  112. package/dist/client/robots.txt +2 -0
  113. package/dist/server/.dev.vars +5 -0
  114. package/dist/server/.vite/manifest.json +2528 -0
  115. package/dist/server/assets/ProductCard-CUPXy5Eo.js +149 -0
  116. package/dist/server/assets/_tanstack-start-manifest_v-do7vTWFD.js +4 -0
  117. package/dist/server/assets/about-TfKQw0Ga.js +28 -0
  118. package/dist/server/assets/admin-DspfJOJk.js +578 -0
  119. package/dist/server/assets/admin.functions-B78ppWLR.js +645 -0
  120. package/dist/server/assets/admin.functions-BWlKBoTL.js +93 -0
  121. package/dist/server/assets/admin.login-CV7QfeA6.js +139 -0
  122. package/dist/server/assets/affiliate-disclosure-B1wI1cDb.js +86 -0
  123. package/dist/server/assets/auth-middleware-Cn49MidW.js +62 -0
  124. package/dist/server/assets/categories-Z7jnAYZP.js +108 -0
  125. package/dist/server/assets/category._slug-D0XY3FGK.js +112 -0
  126. package/dist/server/assets/contact-IzyONsXs.js +104 -0
  127. package/dist/server/assets/faq-aRhB_CR3.js +133 -0
  128. package/dist/server/assets/hero-bg-BP2eVUIX.jpg +0 -0
  129. package/dist/server/assets/index-BTPHbXw9.js +221 -0
  130. package/dist/server/assets/index-ByJkHkrU.js +30 -0
  131. package/dist/server/assets/login-Dvy5Dm0f.js +175 -0
  132. package/dist/server/assets/logo-pSNfLJQk.png +0 -0
  133. package/dist/server/assets/privacy-B6Wiez1P.js +93 -0
  134. package/dist/server/assets/product-links-CGYEPP56.js +16 -0
  135. package/dist/server/assets/product._id-BpRa-1z0.js +231 -0
  136. package/dist/server/assets/products.functions-DSlmibYN.js +209 -0
  137. package/dist/server/assets/products.functions-DlHkRiqi.js +24 -0
  138. package/dist/server/assets/profile-B0NWzVAZ.js +314 -0
  139. package/dist/server/assets/reset-password-CY-rmqMr.js +115 -0
  140. package/dist/server/assets/saved-7FA6Dbom.js +126 -0
  141. package/dist/server/assets/search-Yw5c_fZa.js +329 -0
  142. package/dist/server/assets/signup-UPzgZo4i.js +143 -0
  143. package/dist/server/assets/styles-DrNJG0BO.css +1 -0
  144. package/dist/server/assets/terms-CMnX95bP.js +89 -0
  145. package/dist/server/assets/update-password-Cr94ea8n.js +131 -0
  146. package/dist/server/assets/vendor-aria-hidden-DPa16MWu.js +122 -0
  147. package/dist/server/assets/vendor-class-variance-authority-0YxJPB9Y.js +44 -0
  148. package/dist/server/assets/vendor-cloudflare-unenv-preset-ya0VEFBz.js +250 -0
  149. package/dist/server/assets/vendor-clsx-DgYk2OaC.js +16 -0
  150. package/dist/server/assets/vendor-cookie-es-DAoofYiI.js +44 -0
  151. package/dist/server/assets/vendor-detect-node-es-l0sNRNKZ.js +1 -0
  152. package/dist/server/assets/vendor-floating-ui-core-3tkK0THV.js +726 -0
  153. package/dist/server/assets/vendor-floating-ui-dom-C-cPtgJv.js +626 -0
  154. package/dist/server/assets/vendor-floating-ui-react-dom-CRG6gBpH.js +319 -0
  155. package/dist/server/assets/vendor-floating-ui-utils-DmXANH-E.js +320 -0
  156. package/dist/server/assets/vendor-framer-motion-X4zAkX3J.js +1979 -0
  157. package/dist/server/assets/vendor-get-nonce-DiSj3EHl.js +9 -0
  158. package/dist/server/assets/vendor-h3-v2-CCobnLY5.js +287 -0
  159. package/dist/server/assets/vendor-iceberg-js-bHCkXyJn.js +534 -0
  160. package/dist/server/assets/vendor-isbot-CZ7WjwVs.js +21 -0
  161. package/dist/server/assets/vendor-lovable.dev-cloud-auth-js-BE03njZw.js +180 -0
  162. package/dist/server/assets/vendor-lucide-react-Ddew6HYb.js +458 -0
  163. package/dist/server/assets/vendor-motion-dom-D2MTwGIG.js +5983 -0
  164. package/dist/server/assets/vendor-motion-utils-LJlIFN6m.js +161 -0
  165. package/dist/server/assets/vendor-radix-ui-primitive-B-mNdDrH.js +11 -0
  166. package/dist/server/assets/vendor-radix-ui-react-accordion-1Izf6x00.js +308 -0
  167. package/dist/server/assets/vendor-radix-ui-react-arrow-B882lnFK.js +23 -0
  168. package/dist/server/assets/vendor-radix-ui-react-avatar-BVgZt2Ab.js +209 -0
  169. package/dist/server/assets/vendor-radix-ui-react-collapsible-DCBbMZiS.js +147 -0
  170. package/dist/server/assets/vendor-radix-ui-react-collection-BZ2srfgU.js +150 -0
  171. package/dist/server/assets/vendor-radix-ui-react-compose-refs-D3qsKVk1.js +39 -0
  172. package/dist/server/assets/vendor-radix-ui-react-context-BVoNDLue.js +78 -0
  173. package/dist/server/assets/vendor-radix-ui-react-dialog-DlxMaNYK.js +406 -0
  174. package/dist/server/assets/vendor-radix-ui-react-direction-Dt_WDL1t.js +9 -0
  175. package/dist/server/assets/vendor-radix-ui-react-dismissable-layer-CjsuPohV.js +210 -0
  176. package/dist/server/assets/vendor-radix-ui-react-dropdown-menu-DVxKumY8.js +263 -0
  177. package/dist/server/assets/vendor-radix-ui-react-focus-guards-D_6NoePE.js +29 -0
  178. package/dist/server/assets/vendor-radix-ui-react-focus-scope-DEIhTJJH.js +206 -0
  179. package/dist/server/assets/vendor-radix-ui-react-id-DFFpgh6m.js +14 -0
  180. package/dist/server/assets/vendor-radix-ui-react-menu-CiTMLwjT.js +893 -0
  181. package/dist/server/assets/vendor-radix-ui-react-popper-23Ye2Vyc.js +286 -0
  182. package/dist/server/assets/vendor-radix-ui-react-portal-CZCH5uPk.js +16 -0
  183. package/dist/server/assets/vendor-radix-ui-react-presence-CaAULlDU.js +128 -0
  184. package/dist/server/assets/vendor-radix-ui-react-primitive-BeOk3UYa.js +124 -0
  185. package/dist/server/assets/vendor-radix-ui-react-roving-focus-DES9GR8l.js +224 -0
  186. package/dist/server/assets/vendor-radix-ui-react-slot-DUhZbzoH.js +103 -0
  187. package/dist/server/assets/vendor-radix-ui-react-use-callback-ref-BynBgohw.js +11 -0
  188. package/dist/server/assets/vendor-radix-ui-react-use-controllable-state-C9KpT6DG.js +69 -0
  189. package/dist/server/assets/vendor-radix-ui-react-use-effect-event-gpNY2xjS.js +1 -0
  190. package/dist/server/assets/vendor-radix-ui-react-use-escape-keydown-CcYRQ2pp.js +17 -0
  191. package/dist/server/assets/vendor-radix-ui-react-use-is-hydrated-D_LcBPXY.js +15 -0
  192. package/dist/server/assets/vendor-radix-ui-react-use-layout-effect-1LNLXAjr.js +6 -0
  193. package/dist/server/assets/vendor-radix-ui-react-use-size-D6fiKJQo.js +39 -0
  194. package/dist/server/assets/vendor-react-DvBrY0qp.js +511 -0
  195. package/dist/server/assets/vendor-react-dom-yvMLPM0j.js +10484 -0
  196. package/dist/server/assets/vendor-react-remove-scroll-BNtiEvVN.js +328 -0
  197. package/dist/server/assets/vendor-react-remove-scroll-bar-hLqRASRk.js +82 -0
  198. package/dist/server/assets/vendor-react-style-singleton-BXjcXskB.js +69 -0
  199. package/dist/server/assets/vendor-rou3-3NaGPdI8.js +8 -0
  200. package/dist/server/assets/vendor-seroval-dJyC-Zhz.js +1775 -0
  201. package/dist/server/assets/vendor-seroval-plugins-Pq_U2meB.js +58 -0
  202. package/dist/server/assets/vendor-sonner-CqbjhsRh.js +1086 -0
  203. package/dist/server/assets/vendor-srvx-BA-baEX9.js +6 -0
  204. package/dist/server/assets/vendor-supabase-auth-js-D4xjVprw.js +7602 -0
  205. package/dist/server/assets/vendor-supabase-functions-js-sWy4UYn1.js +322 -0
  206. package/dist/server/assets/vendor-supabase-phoenix-Bw3Uh2Nn.js +1777 -0
  207. package/dist/server/assets/vendor-supabase-postgrest-js-AO-BXa7I.js +4938 -0
  208. package/dist/server/assets/vendor-supabase-realtime-js-BtdNgJbm.js +2111 -0
  209. package/dist/server/assets/vendor-supabase-storage-js-Dk_MrPYO.js +2679 -0
  210. package/dist/server/assets/vendor-supabase-supabase-js-D1EEtG3j.js +697 -0
  211. package/dist/server/assets/vendor-tailwind-merge-BHb_obmC.js +3255 -0
  212. package/dist/server/assets/vendor-tanstack-history-C4pKJmkt.js +204 -0
  213. package/dist/server/assets/vendor-tanstack-query-core-PwwTR5ld.js +2552 -0
  214. package/dist/server/assets/vendor-tanstack-react-query-hhHzXAK1.js +190 -0
  215. package/dist/server/assets/vendor-tanstack-react-router-XzqpA65A.js +1120 -0
  216. package/dist/server/assets/vendor-tanstack-react-start-RvWUpvat.js +37 -0
  217. package/dist/server/assets/vendor-tanstack-react-start-client-gpNY2xjS.js +1 -0
  218. package/dist/server/assets/vendor-tanstack-react-start-server-uj_Y9pEN.js +15 -0
  219. package/dist/server/assets/vendor-tanstack-react-store-gpNY2xjS.js +1 -0
  220. package/dist/server/assets/vendor-tanstack-router-core-6wywV3KN.js +4252 -0
  221. package/dist/server/assets/vendor-tanstack-start-client-core-DoOKV2pA.js +1741 -0
  222. package/dist/server/assets/vendor-tanstack-start-fn-stubs-l0sNRNKZ.js +1 -0
  223. package/dist/server/assets/vendor-tanstack-start-server-core-CsAstXv7.js +1421 -0
  224. package/dist/server/assets/vendor-tanstack-start-storage-context-DgH9hIJT.js +17 -0
  225. package/dist/server/assets/vendor-tanstack-store-l0sNRNKZ.js +1 -0
  226. package/dist/server/assets/vendor-tslib-_8ICaZ64.js +67 -0
  227. package/dist/server/assets/vendor-unenv-DUvF4YIF.js +544 -0
  228. package/dist/server/assets/vendor-use-callback-ref-DMFDRvmi.js +66 -0
  229. package/dist/server/assets/vendor-use-sidecar-DG1tHua4.js +106 -0
  230. package/dist/server/assets/vendor-use-sync-external-store-rZ8vi0It.js +64 -0
  231. package/dist/server/assets/vendor-vercel-analytics-oP8BDp0L.js +168 -0
  232. package/dist/server/assets/vendor-zod-BRyQdbC-.js +3580 -0
  233. package/dist/server/index.js +158 -0
  234. package/dist/server/wrangler.json +1 -0
  235. package/enable-powershell.ps1 +7 -0
  236. package/eslint.config.js +41 -0
  237. package/lint.bat +4 -0
  238. package/package.json +95 -0
  239. package/public/robots.txt +2 -0
  240. package/run-npm-build.cjs +20 -0
  241. package/run-npm-build.js +20 -0
  242. package/src/assets/hero-bg.jpg +0 -0
  243. package/src/assets/logo.png +0 -0
  244. package/src/components/scrollsy/Footer.tsx +68 -0
  245. package/src/components/scrollsy/LiveTicker.tsx +31 -0
  246. package/src/components/scrollsy/Logo.tsx +28 -0
  247. package/src/components/scrollsy/Nav.tsx +255 -0
  248. package/src/components/scrollsy/ProductCard.tsx +190 -0
  249. package/src/components/scrollsy/ProductFilters.tsx +226 -0
  250. package/src/components/scrollsy/SupportWidget.tsx +197 -0
  251. package/src/components/ui/accordion.tsx +51 -0
  252. package/src/components/ui/alert-dialog.tsx +115 -0
  253. package/src/components/ui/alert.tsx +49 -0
  254. package/src/components/ui/aspect-ratio.tsx +5 -0
  255. package/src/components/ui/avatar.tsx +47 -0
  256. package/src/components/ui/badge.tsx +32 -0
  257. package/src/components/ui/breadcrumb.tsx +101 -0
  258. package/src/components/ui/button.tsx +49 -0
  259. package/src/components/ui/calendar.tsx +177 -0
  260. package/src/components/ui/card.tsx +55 -0
  261. package/src/components/ui/carousel.tsx +240 -0
  262. package/src/components/ui/chart.tsx +331 -0
  263. package/src/components/ui/checkbox.tsx +26 -0
  264. package/src/components/ui/collapsible.tsx +11 -0
  265. package/src/components/ui/command.tsx +143 -0
  266. package/src/components/ui/context-menu.tsx +187 -0
  267. package/src/components/ui/dialog.tsx +104 -0
  268. package/src/components/ui/drawer.tsx +98 -0
  269. package/src/components/ui/dropdown-menu.tsx +188 -0
  270. package/src/components/ui/form.tsx +171 -0
  271. package/src/components/ui/hover-card.tsx +27 -0
  272. package/src/components/ui/input-otp.tsx +69 -0
  273. package/src/components/ui/input.tsx +22 -0
  274. package/src/components/ui/label.tsx +21 -0
  275. package/src/components/ui/menubar.tsx +229 -0
  276. package/src/components/ui/navigation-menu.tsx +120 -0
  277. package/src/components/ui/pagination.tsx +98 -0
  278. package/src/components/ui/popover.tsx +31 -0
  279. package/src/components/ui/progress.tsx +25 -0
  280. package/src/components/ui/radio-group.tsx +36 -0
  281. package/src/components/ui/resizable.tsx +37 -0
  282. package/src/components/ui/scroll-area.tsx +44 -0
  283. package/src/components/ui/select.tsx +152 -0
  284. package/src/components/ui/separator.tsx +24 -0
  285. package/src/components/ui/sheet.tsx +122 -0
  286. package/src/components/ui/sidebar.tsx +744 -0
  287. package/src/components/ui/skeleton.tsx +7 -0
  288. package/src/components/ui/slider.tsx +23 -0
  289. package/src/components/ui/sonner.tsx +23 -0
  290. package/src/components/ui/switch.tsx +27 -0
  291. package/src/components/ui/table.tsx +94 -0
  292. package/src/components/ui/tabs.tsx +53 -0
  293. package/src/components/ui/textarea.tsx +21 -0
  294. package/src/components/ui/toggle-group.tsx +57 -0
  295. package/src/components/ui/toggle.tsx +42 -0
  296. package/src/components/ui/tooltip.tsx +32 -0
  297. package/src/hooks/use-auth.ts +26 -0
  298. package/src/hooks/use-mobile.tsx +19 -0
  299. package/src/integrations/lovable/index.ts +41 -0
  300. package/src/lib/admin.functions.ts +564 -0
  301. package/src/lib/error-capture.ts +27 -0
  302. package/src/lib/error-page.ts +30 -0
  303. package/src/lib/product-links.ts +39 -0
  304. package/src/lib/products.functions.ts +101 -0
  305. package/src/lib/utils.ts +6 -0
  306. package/src/routeTree.gen.ts +480 -0
  307. package/src/router.tsx +16 -0
  308. package/src/routes/__root.tsx +177 -0
  309. package/src/routes/about.tsx +66 -0
  310. package/src/routes/admin.login.tsx +95 -0
  311. package/src/routes/admin.tsx +811 -0
  312. package/src/routes/affiliate-disclosure.tsx +35 -0
  313. package/src/routes/categories.tsx +57 -0
  314. package/src/routes/category.$slug.tsx +51 -0
  315. package/src/routes/contact.tsx +69 -0
  316. package/src/routes/faq.tsx +63 -0
  317. package/src/routes/index.tsx +269 -0
  318. package/src/routes/login.tsx +160 -0
  319. package/src/routes/privacy.tsx +39 -0
  320. package/src/routes/product.$id.tsx +212 -0
  321. package/src/routes/profile.tsx +393 -0
  322. package/src/routes/reset-password.tsx +71 -0
  323. package/src/routes/saved.tsx +83 -0
  324. package/src/routes/search.tsx +136 -0
  325. package/src/routes/signup.tsx +108 -0
  326. package/src/routes/sitemap[.]xml.ts +34 -0
  327. package/src/routes/terms.tsx +40 -0
  328. package/src/routes/update-password.tsx +91 -0
  329. package/src/server.ts +80 -0
  330. package/src/start.ts +24 -0
  331. package/src/styles.css +333 -0
  332. package/terminal-test-output.txt +1 -0
  333. package/tsconfig.json +27 -0
  334. package/vercel.json +26 -0
  335. package/vite.config.ts +38 -0
  336. package/wrangler.jsonc +7 -0
@@ -0,0 +1,35 @@
1
+ import { createFileRoute } from "@tanstack/react-router";
2
+ import { Prose } from "./about";
3
+
4
+ export const Route = createFileRoute("/affiliate-disclosure")({
5
+ head: () => ({
6
+ meta: [
7
+ { title: "Affiliate Disclosure · Scrollsy" },
8
+ { name: "description", content: "How Scrollsy makes money — full affiliate disclosure." },
9
+ ],
10
+ }),
11
+ component: () => (
12
+ <Prose title="Affiliate Disclosure" subtitle="Full transparency, always.">
13
+ <p>
14
+ <strong>Scrollsy may earn commissions from purchases made through affiliate links.</strong>
15
+ </p>
16
+ <p>
17
+ When you click "View Deal" on a product, you'll be sent to a partner retailer — for example
18
+ AliExpress, Amazon, Temu or Kilimall. If you buy something during that visit, Scrollsy may
19
+ receive a small commission from the retailer.
20
+ </p>
21
+ <h2>Does this cost you anything?</h2>
22
+ <p>
23
+ Nope. Affiliate commissions are paid by the retailer out of their margin. You pay the same
24
+ price you would have paid going to the retailer directly.
25
+ </p>
26
+ <h2>Does it influence what we feature?</h2>
27
+ <p>
28
+ We feature products based on virality, quality and price — not commission rate. Our
29
+ editorial picks stay independent of any merchant relationship.
30
+ </p>
31
+ <h2>Want to learn more?</h2>
32
+ <p>Email us at affiliates@scrollsy.app.</p>
33
+ </Prose>
34
+ ),
35
+ });
@@ -0,0 +1,57 @@
1
+ import { createFileRoute, Link } from "@tanstack/react-router";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import { getCategories } from "@/lib/products.functions";
4
+
5
+ export const Route = createFileRoute("/categories")({
6
+ head: () => ({
7
+ meta: [
8
+ { title: "Categories · Scrollsy" },
9
+ {
10
+ name: "description",
11
+ content:
12
+ "Browse Scrollsy product categories — tech, fashion, smart home, gaming, viral finds and more.",
13
+ },
14
+ ],
15
+ links: [{ rel: "canonical", href: "/categories" }],
16
+ }),
17
+ component: Categories,
18
+ });
19
+
20
+ function Categories() {
21
+ const { data } = useQuery({ queryKey: ["cats"], queryFn: () => getCategories() });
22
+ return (
23
+ <div className="px-4 max-w-7xl mx-auto pt-10">
24
+ <h1 className="font-display text-4xl md:text-5xl font-bold tracking-tight">
25
+ Explore <span className="text-gradient">categories</span>
26
+ </h1>
27
+ <p className="text-muted-foreground mt-2">
28
+ Every aesthetic. Every obsession. One scroll away.
29
+ </p>
30
+
31
+ <div className="mt-10 grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
32
+ {(data ?? []).map((c, i) => (
33
+ <Link
34
+ key={c.id}
35
+ to="/category/$slug"
36
+ params={{ slug: c.slug }}
37
+ className="group glass rounded-3xl p-6 aspect-square flex flex-col justify-between hover:scale-[1.03] transition-transform relative overflow-hidden"
38
+ >
39
+ <div
40
+ className="absolute -top-10 -right-10 w-40 h-40 rounded-full opacity-40 group-hover:opacity-70 transition-opacity blur-2xl"
41
+ style={{
42
+ background: `conic-gradient(from ${i * 40}deg, #ff6b35, #e84393, #6c5ce7, #ff6b35)`,
43
+ }}
44
+ />
45
+ <div className="relative">
46
+ <div className="text-xs text-muted-foreground uppercase tracking-wider">Category</div>
47
+ <div className="font-display text-2xl font-bold mt-2">{c.name}</div>
48
+ </div>
49
+ <div className="relative text-xs text-muted-foreground group-hover:text-foreground transition-colors">
50
+ Explore →
51
+ </div>
52
+ </Link>
53
+ ))}
54
+ </div>
55
+ </div>
56
+ );
57
+ }
@@ -0,0 +1,51 @@
1
+ import { createFileRoute, Link, notFound } from "@tanstack/react-router";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import { getProductsByCategory } from "@/lib/products.functions";
4
+ import { ProductCard } from "@/components/scrollsy/ProductCard";
5
+ import { ChevronLeft } from "lucide-react";
6
+
7
+ export const Route = createFileRoute("/category/$slug")({
8
+ head: ({ params }) => ({
9
+ meta: [
10
+ { title: `${params.slug.replace(/-/g, " ")} · Scrollsy` },
11
+ {
12
+ name: "description",
13
+ content: `Browse trending ${params.slug.replace(/-/g, " ")} finds on Scrollsy.`,
14
+ },
15
+ ],
16
+ }),
17
+ component: CategoryPage,
18
+ });
19
+
20
+ function CategoryPage() {
21
+ const { slug } = Route.useParams();
22
+ const { data, isLoading } = useQuery({
23
+ queryKey: ["cat", slug],
24
+ queryFn: () => getProductsByCategory({ data: { slug } }),
25
+ });
26
+
27
+ if (!isLoading && !data?.category) throw notFound();
28
+
29
+ return (
30
+ <div className="px-4 max-w-7xl mx-auto pt-10">
31
+ <Link
32
+ to="/categories"
33
+ className="inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground"
34
+ >
35
+ <ChevronLeft className="w-4 h-4" /> All categories
36
+ </Link>
37
+ <h1 className="mt-3 font-display text-4xl md:text-5xl font-bold tracking-tight">
38
+ {data?.category?.name ?? "…"}
39
+ </h1>
40
+ <p className="text-muted-foreground mt-1">
41
+ {data?.products.length ?? 0} trending finds in this drop
42
+ </p>
43
+
44
+ <div className="mt-8 grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
45
+ {(data?.products ?? []).map((p) => (
46
+ <ProductCard key={p.id} product={p} aspect="tall" />
47
+ ))}
48
+ </div>
49
+ </div>
50
+ );
51
+ }
@@ -0,0 +1,69 @@
1
+ import { createFileRoute } from "@tanstack/react-router";
2
+ import { Prose } from "./about";
3
+ import { useState } from "react";
4
+ import { Mail, Send } from "lucide-react";
5
+ import { toast } from "sonner";
6
+
7
+ export const Route = createFileRoute("/contact")({
8
+ head: () => ({
9
+ meta: [
10
+ { title: "Contact · Scrollsy" },
11
+ {
12
+ name: "description",
13
+ content:
14
+ "Get in touch with the Scrollsy team — partnerships, press, support and creator collabs.",
15
+ },
16
+ ],
17
+ }),
18
+ component: Contact,
19
+ });
20
+
21
+ function Contact() {
22
+ const [email, setEmail] = useState("");
23
+ const [message, setMessage] = useState("");
24
+
25
+ const send = (e: React.FormEvent) => {
26
+ e.preventDefault();
27
+ toast.success("Thanks! We'll be in touch soon.");
28
+ setEmail("");
29
+ setMessage("");
30
+ };
31
+
32
+ return (
33
+ <Prose
34
+ title="Get in touch"
35
+ subtitle="Partnerships, press, support or creator collabs — we'd love to hear from you."
36
+ >
37
+ <form onSubmit={send} className="not-prose mt-8 space-y-3">
38
+ <label className="flex items-center gap-2 glass rounded-xl px-3 py-2.5">
39
+ <Mail className="w-4 h-4 text-muted-foreground" />
40
+ <input
41
+ required
42
+ type="email"
43
+ value={email}
44
+ onChange={(e) => setEmail(e.target.value)}
45
+ placeholder="Your email"
46
+ className="flex-1 bg-transparent outline-none text-sm"
47
+ />
48
+ </label>
49
+ <textarea
50
+ required
51
+ value={message}
52
+ onChange={(e) => setMessage(e.target.value)}
53
+ placeholder="How can we help?"
54
+ rows={5}
55
+ className="w-full glass rounded-xl px-3 py-2.5 text-sm bg-transparent outline-none resize-none"
56
+ />
57
+ <button className="btn-glow rounded-full px-6 py-3 font-semibold text-sm inline-flex items-center gap-2">
58
+ <Send className="w-4 h-4" /> Send message
59
+ </button>
60
+ </form>
61
+ <p className="text-sm mt-6">
62
+ Or email us directly at{" "}
63
+ <a href="mailto:hello@scrollsy.app" className="text-gradient font-semibold">
64
+ hello@scrollsy.app
65
+ </a>
66
+ </p>
67
+ </Prose>
68
+ );
69
+ }
@@ -0,0 +1,63 @@
1
+ import { createFileRoute } from "@tanstack/react-router";
2
+ import { Prose } from "./about";
3
+ import {
4
+ Accordion,
5
+ AccordionContent,
6
+ AccordionItem,
7
+ AccordionTrigger,
8
+ } from "@/components/ui/accordion";
9
+
10
+ const faqs = [
11
+ {
12
+ q: "What is Scrollsy?",
13
+ a: "Scrollsy is a visual product discovery platform that surfaces trending gadgets, viral finds and the best deals from across the web.",
14
+ },
15
+ {
16
+ q: "Does Scrollsy sell products directly?",
17
+ a: "No — when you tap 'View Deal' we send you to the retailer (AliExpress, Amazon, Temu, Kilimall, etc.) where you complete your purchase as normal.",
18
+ },
19
+ {
20
+ q: "How do you earn money?",
21
+ a: "Scrollsy may earn a small commission from purchases made through our affiliate links. It costs you nothing extra and keeps Scrollsy free to use.",
22
+ },
23
+ { q: "Is Scrollsy free?", a: "Yes — totally free for shoppers. Always." },
24
+ {
25
+ q: "How do I save products?",
26
+ a: "Tap the heart on any product card. Saved items live in your Saved page (you'll need a free account).",
27
+ },
28
+ {
29
+ q: "How are trending products selected?",
30
+ a: "Our team curates each drop based on virality signals (TikTok, Pinterest, social shares) plus real shopper engagement on Scrollsy.",
31
+ },
32
+ {
33
+ q: "Are the deals real?",
34
+ a: "Yes. We verify discount pricing at the time of curation, but stock and pricing on third-party retailers can change without notice.",
35
+ },
36
+ ];
37
+
38
+ export const Route = createFileRoute("/faq")({
39
+ head: () => ({
40
+ meta: [
41
+ { title: "FAQ · Scrollsy" },
42
+ {
43
+ name: "description",
44
+ content:
45
+ "Frequently asked questions about Scrollsy — affiliate links, deals, accounts and more.",
46
+ },
47
+ ],
48
+ }),
49
+ component: () => (
50
+ <Prose title="Questions, answered" subtitle="Everything you might want to know about Scrollsy.">
51
+ <Accordion type="single" collapsible className="not-prose mt-6 space-y-2">
52
+ {faqs.map((f, i) => (
53
+ <AccordionItem key={i} value={`f${i}`} className="glass rounded-2xl px-4 border-none">
54
+ <AccordionTrigger className="text-left font-display font-semibold hover:no-underline">
55
+ {f.q}
56
+ </AccordionTrigger>
57
+ <AccordionContent className="text-muted-foreground">{f.a}</AccordionContent>
58
+ </AccordionItem>
59
+ ))}
60
+ </Accordion>
61
+ </Prose>
62
+ ),
63
+ });
@@ -0,0 +1,269 @@
1
+ import { createFileRoute, Link } from "@tanstack/react-router";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import { Search, Sparkles, TrendingUp, Flame, Zap, ArrowRight } from "lucide-react";
4
+ import { getHomeData } from "@/lib/products.functions";
5
+ import { ProductCard } from "@/components/scrollsy/ProductCard";
6
+ import { LiveTicker } from "@/components/scrollsy/LiveTicker";
7
+ import heroBg from "@/assets/hero-bg.jpg";
8
+ import { useState } from "react";
9
+ import { useNavigate } from "@tanstack/react-router";
10
+
11
+ export const Route = createFileRoute("/")({
12
+ head: () => ({
13
+ meta: [
14
+ { title: "Scrollsy — Scroll. Discover. Save." },
15
+ {
16
+ name: "description",
17
+ content:
18
+ "Discover viral gadgets, trending finds and hidden deals on Scrollsy — the visual marketplace built for endless scrolling.",
19
+ },
20
+ { property: "og:title", content: "Scrollsy — Scroll. Discover. Save." },
21
+ { property: "og:image", content: heroBg },
22
+ ],
23
+ links: [{ rel: "canonical", href: "/" }],
24
+ }),
25
+ component: Home,
26
+ });
27
+
28
+ function Home() {
29
+ const { data } = useQuery({ queryKey: ["home"], queryFn: () => getHomeData() });
30
+ const [q, setQ] = useState("");
31
+ const nav = useNavigate();
32
+
33
+ const featured = data?.featured ?? [];
34
+ const trending = data?.trending ?? [];
35
+ const recent = data?.recent ?? [];
36
+ const categories = data?.categories ?? [];
37
+
38
+ return (
39
+ <div className="px-4">
40
+ {/* HERO */}
41
+ <section className="relative max-w-7xl mx-auto rounded-[2rem] overflow-hidden mt-4 border border-white/5">
42
+ <img
43
+ src={heroBg}
44
+ alt=""
45
+ className="absolute inset-0 w-full h-full object-cover opacity-60"
46
+ width={1920}
47
+ height={1080}
48
+ />
49
+ <div className="absolute inset-0 bg-gradient-to-b from-background/30 via-background/40 to-background" />
50
+ <div className="absolute inset-0 bg-gradient-radial" />
51
+
52
+ <div className="relative z-10 px-6 md:px-14 py-20 md:py-28 max-w-4xl">
53
+ <div className="mb-6">
54
+ <LiveTicker />
55
+ </div>
56
+
57
+ <h1 className="font-display text-5xl md:text-7xl font-bold leading-[1.02] tracking-tight">
58
+ Scroll. Discover. <span className="text-gradient animate-gradient">Save.</span>
59
+ </h1>
60
+ <p className="mt-5 text-lg md:text-xl text-muted-foreground max-w-xl">
61
+ The visual marketplace of the internet's hidden gems. Viral gadgets, smart finds and
62
+ trending deals — curated for endless scrolling.
63
+ </p>
64
+
65
+ <form
66
+ onSubmit={(e) => {
67
+ e.preventDefault();
68
+ nav({ to: "/search", search: { q } });
69
+ }}
70
+ className="mt-8 max-w-xl flex glass-strong rounded-2xl p-1.5 shadow-elevated"
71
+ >
72
+ <div className="flex items-center gap-2 px-3 text-muted-foreground">
73
+ <Search className="w-5 h-5" />
74
+ </div>
75
+ <input
76
+ value={q}
77
+ onChange={(e) => setQ(e.target.value)}
78
+ placeholder="Search products, gadgets, viral finds…"
79
+ className="flex-1 bg-transparent outline-none text-sm placeholder:text-muted-foreground py-3"
80
+ />
81
+ <button
82
+ type="submit"
83
+ className="btn-glow rounded-xl px-5 py-2.5 text-sm font-semibold inline-flex items-center gap-1.5"
84
+ >
85
+ <Sparkles className="w-4 h-4" /> Discover
86
+ </button>
87
+ </form>
88
+
89
+ <div className="mt-8 flex flex-wrap gap-2">
90
+ {categories.slice(0, 8).map((c) => (
91
+ <Link
92
+ key={c.id}
93
+ to="/category/$slug"
94
+ params={{ slug: c.slug }}
95
+ className="glass rounded-full px-3.5 py-1.5 text-xs font-medium hover:bg-white/10 transition"
96
+ >
97
+ {c.name}
98
+ </Link>
99
+ ))}
100
+ </div>
101
+ </div>
102
+ </section>
103
+
104
+ {/* FEATURED BENTO */}
105
+ {featured.length > 0 && (
106
+ <Section
107
+ eyebrow={
108
+ <>
109
+ <Zap className="w-3.5 h-3.5" /> Featured today
110
+ </>
111
+ }
112
+ title="Most popular right now"
113
+ subtitle="Hand-picked finds the community is loving"
114
+ href="/search"
115
+ >
116
+ <BentoGrid products={featured} />
117
+ </Section>
118
+ )}
119
+
120
+ {/* CATEGORY SLIDER */}
121
+ <Section title="Shop by mood" eyebrow={<>Categories</>}>
122
+ <div className="flex gap-3 overflow-x-auto pb-3 -mx-4 px-4 snap-x">
123
+ {categories.map((c, i) => (
124
+ <Link
125
+ key={c.id}
126
+ to="/category/$slug"
127
+ params={{ slug: c.slug }}
128
+ className="snap-start shrink-0 glass rounded-2xl px-5 py-4 hover:scale-105 transition-transform min-w-[140px]"
129
+ style={{
130
+ background: `linear-gradient(135deg, hsl(${(i * 27) % 360} 80% 55% / 0.18), transparent)`,
131
+ }}
132
+ >
133
+ <div className="text-xs text-muted-foreground">Category</div>
134
+ <div className="text-base font-display font-semibold mt-1">{c.name}</div>
135
+ </Link>
136
+ ))}
137
+ </div>
138
+ </Section>
139
+
140
+ {/* TRENDING */}
141
+ {trending.length > 0 && (
142
+ <Section
143
+ eyebrow={
144
+ <>
145
+ <TrendingUp className="w-3.5 h-3.5" /> Trending
146
+ </>
147
+ }
148
+ title="Trending on TikTok"
149
+ subtitle="Going viral right now"
150
+ href="/search"
151
+ >
152
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
153
+ {trending.slice(0, 8).map((p) => (
154
+ <ProductCard key={p.id} product={p} aspect="tall" />
155
+ ))}
156
+ </div>
157
+ </Section>
158
+ )}
159
+
160
+ {/* BEST DEALS BANNER */}
161
+ <section className="max-w-7xl mx-auto mt-16 rounded-3xl overflow-hidden relative border border-white/5">
162
+ <div className="bg-gradient-brand animate-gradient p-8 md:p-12">
163
+ <div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4">
164
+ <div>
165
+ <span className="inline-flex items-center gap-1.5 text-xs font-semibold bg-black/30 rounded-full px-3 py-1">
166
+ <Flame className="w-3.5 h-3.5" /> Hot deals
167
+ </span>
168
+ <h2 className="font-display text-3xl md:text-4xl font-bold mt-3 text-white">
169
+ Up to 70% off · today only
170
+ </h2>
171
+ <p className="text-white/90 mt-2 max-w-md">
172
+ Limited drops curated from across the web. When they're gone, they're gone.
173
+ </p>
174
+ </div>
175
+ <Link
176
+ to="/search"
177
+ search={{ q: "" }}
178
+ className="bg-black text-white rounded-full px-5 py-3 font-semibold text-sm inline-flex items-center gap-2 hover:bg-black/80 transition"
179
+ >
180
+ Shop deals <ArrowRight className="w-4 h-4" />
181
+ </Link>
182
+ </div>
183
+ </div>
184
+ </section>
185
+
186
+ {/* MASONRY FEED */}
187
+ {recent.length > 0 && (
188
+ <Section
189
+ eyebrow={<>Fresh drops</>}
190
+ title="Recently added"
191
+ subtitle="The newest finds, just in"
192
+ >
193
+ <div className="columns-2 md:columns-3 lg:columns-4 gap-4 space-y-4 [&>*]:break-inside-avoid">
194
+ {recent.map((p, i) => (
195
+ <ProductCard
196
+ key={p.id}
197
+ product={p}
198
+ aspect={(["tall", "square", "wide", "tall"] as const)[i % 4]}
199
+ />
200
+ ))}
201
+ </div>
202
+ </Section>
203
+ )}
204
+ </div>
205
+ );
206
+ }
207
+
208
+ function Section({
209
+ title,
210
+ subtitle,
211
+ eyebrow,
212
+ href,
213
+ children,
214
+ }: {
215
+ title: string;
216
+ subtitle?: string;
217
+ eyebrow?: React.ReactNode;
218
+ href?: string;
219
+ children: React.ReactNode;
220
+ }) {
221
+ return (
222
+ <section className="max-w-7xl mx-auto mt-16 animate-fade-up">
223
+ <div className="flex items-end justify-between mb-6">
224
+ <div>
225
+ {eyebrow && (
226
+ <div className="inline-flex items-center gap-1.5 text-xs font-semibold text-brand-magenta bg-brand-magenta/10 rounded-full px-3 py-1 mb-2">
227
+ {eyebrow}
228
+ </div>
229
+ )}
230
+ <h2 className="font-display text-2xl md:text-3xl font-bold tracking-tight">{title}</h2>
231
+ {subtitle && <p className="text-sm text-muted-foreground mt-1">{subtitle}</p>}
232
+ </div>
233
+ {href && (
234
+ <Link
235
+ to={href}
236
+ className="text-xs text-muted-foreground hover:text-foreground inline-flex items-center gap-1"
237
+ >
238
+ View all <ArrowRight className="w-3.5 h-3.5" />
239
+ </Link>
240
+ )}
241
+ </div>
242
+ {children}
243
+ </section>
244
+ );
245
+ }
246
+
247
+ function BentoGrid({ products }: { products: any[] }) {
248
+ // 8 products into bento layout
249
+ if (products.length < 4) {
250
+ return (
251
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
252
+ {products.map((p) => (
253
+ <ProductCard key={p.id} product={p} aspect="square" />
254
+ ))}
255
+ </div>
256
+ );
257
+ }
258
+ const [big, ...rest] = products;
259
+ return (
260
+ <div className="grid grid-cols-2 md:grid-cols-4 grid-rows-2 gap-4 md:h-[640px]">
261
+ <div className="col-span-2 row-span-2 md:row-span-2">
262
+ <ProductCard product={big} aspect="auto" className="h-full" priority />
263
+ </div>
264
+ {rest.slice(0, 4).map((p) => (
265
+ <ProductCard key={p.id} product={p} aspect="auto" className="h-full" />
266
+ ))}
267
+ </div>
268
+ );
269
+ }