@unbox-plus/cli 0.20.4

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 (358) hide show
  1. package/LICENSE.md +46 -0
  2. package/README.md +175 -0
  3. package/bin/cli.js +439 -0
  4. package/package.json +43 -0
  5. package/src/colors.js +100 -0
  6. package/src/presets.js +241 -0
  7. package/src/theme.js +253 -0
  8. package/template/.claude/agents/assets-marca.md +40 -0
  9. package/template/.claude/agents/avaliador-visual.md +48 -0
  10. package/template/.claude/agents/branding-briefing.md +401 -0
  11. package/template/.claude/agents/conteudo-secao.md +46 -0
  12. package/template/.claude/agents/paginas-legais.md +44 -0
  13. package/template/.claude/settings.json +3 -0
  14. package/template/.claude/skills/frontend-design/ATTRIBUTION.md +3 -0
  15. package/template/.claude/skills/frontend-design/LICENSE.txt +177 -0
  16. package/template/.claude/skills/frontend-design/SKILL.md +55 -0
  17. package/template/.claude/skills/web-design-guidelines/ATTRIBUTION.md +3 -0
  18. package/template/.claude/skills/web-design-guidelines/SKILL.md +50 -0
  19. package/template/.claude/skills/web-design-guidelines/guidelines-snapshot.md +180 -0
  20. package/template/.env.example +158 -0
  21. package/template/.mcp.json.example +14 -0
  22. package/template/.nvmrc +1 -0
  23. package/template/CLAUDE.md +333 -0
  24. package/template/COVERAGE.md +80 -0
  25. package/template/DEPLOY.md +179 -0
  26. package/template/QA.md +108 -0
  27. package/template/README.md +152 -0
  28. package/template/agents/CONSTRUCAO.md +207 -0
  29. package/template/agents/MANAGER.md +192 -0
  30. package/template/agents/PADROES.md +255 -0
  31. package/template/agents/definitions/00-scaffold.md +56 -0
  32. package/template/agents/definitions/01-layout.md +47 -0
  33. package/template/agents/definitions/02-homepage.md +70 -0
  34. package/template/agents/definitions/03-catalog.md +86 -0
  35. package/template/agents/definitions/04-pdp.md +97 -0
  36. package/template/agents/definitions/05-cart.md +116 -0
  37. package/template/agents/definitions/06-checkout.md +146 -0
  38. package/template/agents/definitions/07-auth.md +130 -0
  39. package/template/agents/definitions/08-customer.md +55 -0
  40. package/template/agents/definitions/09-promotions.md +62 -0
  41. package/template/agents/definitions/10-feedback.md +128 -0
  42. package/template/agents/definitions/11-seo-infra.md +159 -0
  43. package/template/agents/definitions/12-deploy.md +173 -0
  44. package/template/agents/definitions/13-cro.md +275 -0
  45. package/template/agents/definitions/14-seo.md +388 -0
  46. package/template/agents/definitions/15-branding.md +26 -0
  47. package/template/agents/definitions/16-qa-visual.md +138 -0
  48. package/template/agents/definitions/17-aeo.md +257 -0
  49. package/template/app/(loja)/busca/page.tsx +72 -0
  50. package/template/app/(loja)/carrinho/oferta/page.tsx +57 -0
  51. package/template/app/(loja)/carrinho/page.tsx +160 -0
  52. package/template/app/(loja)/categoria/[tagSlug]/layout.tsx +31 -0
  53. package/template/app/(loja)/categoria/[tagSlug]/loading.tsx +17 -0
  54. package/template/app/(loja)/categoria/[tagSlug]/page.tsx +49 -0
  55. package/template/app/(loja)/checkout/page.tsx +80 -0
  56. package/template/app/(loja)/checkout/pix/[ref]/page.tsx +133 -0
  57. package/template/app/(loja)/conta/assinaturas/[referenceId]/page.tsx +112 -0
  58. package/template/app/(loja)/conta/assinaturas/page.tsx +51 -0
  59. package/template/app/(loja)/conta/enderecos/page.tsx +20 -0
  60. package/template/app/(loja)/conta/entrar/page.tsx +119 -0
  61. package/template/app/(loja)/conta/page.tsx +45 -0
  62. package/template/app/(loja)/conta/pedidos/[referenceId]/page.tsx +49 -0
  63. package/template/app/(loja)/conta/pedidos/page.tsx +104 -0
  64. package/template/app/(loja)/conta/preferencias/page.tsx +24 -0
  65. package/template/app/(loja)/devolucoes/page.tsx +36 -0
  66. package/template/app/(loja)/layout.tsx +22 -0
  67. package/template/app/(loja)/oferta/page.tsx +57 -0
  68. package/template/app/(loja)/page.tsx +86 -0
  69. package/template/app/(loja)/pedido/[referenceId]/page.tsx +78 -0
  70. package/template/app/(loja)/privacidade/page.tsx +113 -0
  71. package/template/app/(loja)/produto/[productSlug]/layout.tsx +23 -0
  72. package/template/app/(loja)/produto/[productSlug]/loading.tsx +20 -0
  73. package/template/app/(loja)/produto/[productSlug]/page.tsx +372 -0
  74. package/template/app/(loja)/produtos/loading.tsx +16 -0
  75. package/template/app/(loja)/produtos/page.tsx +33 -0
  76. package/template/app/(loja)/termos/page.tsx +104 -0
  77. package/template/app/acesso/page.tsx +129 -0
  78. package/template/app/api/account/addresses/route.ts +39 -0
  79. package/template/app/api/account/exists/route.ts +21 -0
  80. package/template/app/api/account/me/route.ts +25 -0
  81. package/template/app/api/account/otp/route.ts +26 -0
  82. package/template/app/api/account/preferences/route.ts +19 -0
  83. package/template/app/api/account/signin/route.ts +27 -0
  84. package/template/app/api/account/signout/route.ts +9 -0
  85. package/template/app/api/acesso/route.ts +171 -0
  86. package/template/app/api/capi/route.ts +34 -0
  87. package/template/app/api/cart/coupon/route.ts +49 -0
  88. package/template/app/api/cart/items/route.ts +45 -0
  89. package/template/app/api/cart/link/route.ts +69 -0
  90. package/template/app/api/cart/route.ts +113 -0
  91. package/template/app/api/cart/share/route.ts +21 -0
  92. package/template/app/api/cep/[code]/route.ts +18 -0
  93. package/template/app/api/checkout/address/route.ts +24 -0
  94. package/template/app/api/checkout/email/route.ts +41 -0
  95. package/template/app/api/checkout/installments/route.ts +16 -0
  96. package/template/app/api/checkout/route.ts +110 -0
  97. package/template/app/api/checkout/shipping/route.ts +65 -0
  98. package/template/app/api/checkout-destination/route.ts +31 -0
  99. package/template/app/api/order/[ref]/route.ts +17 -0
  100. package/template/app/api/payment-link/route.ts +28 -0
  101. package/template/app/api/revalidate/route.ts +52 -0
  102. package/template/app/api/shipping/quote/route.ts +29 -0
  103. package/template/app/api/subscriptions/[id]/route.ts +52 -0
  104. package/template/app/api/track/route.ts +28 -0
  105. package/template/app/api/unbox/catalogo/route.ts +136 -0
  106. package/template/app/api/unbox/paginas/route.ts +167 -0
  107. package/template/app/api/unbox/vitrine/route.ts +84 -0
  108. package/template/app/api/webhooks/unbox/route.ts +97 -0
  109. package/template/app/apple-icon.svg +5 -0
  110. package/template/app/error.tsx +15 -0
  111. package/template/app/globals.css +457 -0
  112. package/template/app/icon.svg +4 -0
  113. package/template/app/layout.tsx +90 -0
  114. package/template/app/llms.txt/route.ts +43 -0
  115. package/template/app/manifest.ts +21 -0
  116. package/template/app/not-found.tsx +18 -0
  117. package/template/app/opengraph-image.tsx +35 -0
  118. package/template/app/robots.ts +31 -0
  119. package/template/app/sitemap.ts +42 -0
  120. package/template/bootstrap.sh +87 -0
  121. package/template/components/account/account-shell.tsx +47 -0
  122. package/template/components/account/address-book.tsx +147 -0
  123. package/template/components/account/preferences-form.tsx +77 -0
  124. package/template/components/account/reorder-button.tsx +47 -0
  125. package/template/components/account/signout-button.tsx +20 -0
  126. package/template/components/account/subscription-actions.tsx +204 -0
  127. package/template/components/account-nav.tsx +41 -0
  128. package/template/components/address-fields.tsx +102 -0
  129. package/template/components/analytics/data-layer-ready.tsx +16 -0
  130. package/template/components/analytics/purchase-tracker.tsx +32 -0
  131. package/template/components/brand-search.tsx +34 -0
  132. package/template/components/cart/cart-provider.tsx +340 -0
  133. package/template/components/cart/mini-cart.tsx +228 -0
  134. package/template/components/cart-button.tsx +35 -0
  135. package/template/components/catalog/catalog-client.tsx +523 -0
  136. package/template/components/catalog/grid-skeleton.tsx +19 -0
  137. package/template/components/catalog/pager.tsx +66 -0
  138. package/template/components/catalog/product-grid-card.tsx +52 -0
  139. package/template/components/catalog/product-grid.tsx +27 -0
  140. package/template/components/checkout/checkout-client.tsx +1333 -0
  141. package/template/components/chrome/announce-bar.tsx +57 -0
  142. package/template/components/chrome/chrome-recipe.ts +13 -0
  143. package/template/components/chrome/footers/captura-botao.tsx +28 -0
  144. package/template/components/chrome/footers/colunas.tsx +110 -0
  145. package/template/components/chrome/footers/conversao.tsx +90 -0
  146. package/template/components/chrome/footers/editorial.tsx +98 -0
  147. package/template/components/chrome/footers/minimal.tsx +55 -0
  148. package/template/components/chrome/header-bar-mobile.tsx +64 -0
  149. package/template/components/chrome/headers/centralizado.tsx +71 -0
  150. package/template/components/chrome/headers/classico.tsx +64 -0
  151. package/template/components/chrome/headers/compacto.tsx +66 -0
  152. package/template/components/chrome/headers/equilibrado.tsx +73 -0
  153. package/template/components/chrome/headers/imersivo.tsx +62 -0
  154. package/template/components/chrome/registry.ts +70 -0
  155. package/template/components/chrome/solid-on-scroll.tsx +39 -0
  156. package/template/components/empty-state.tsx +22 -0
  157. package/template/components/home/combo-card-compact.tsx +67 -0
  158. package/template/components/home/combos-home.tsx +64 -0
  159. package/template/components/home/combos-section.tsx +177 -0
  160. package/template/components/home/home-recipe.ts +41 -0
  161. package/template/components/home/sections/attributes-marquee.tsx +178 -0
  162. package/template/components/home/sections/benefits.tsx +80 -0
  163. package/template/components/home/sections/bloco-html.tsx +47 -0
  164. package/template/components/home/sections/catalogo.ts +165 -0
  165. package/template/components/home/sections/category-pills.tsx +32 -0
  166. package/template/components/home/sections/combos-carousel.tsx +141 -0
  167. package/template/components/home/sections/comparison.tsx +73 -0
  168. package/template/components/home/sections/founder-story.tsx +40 -0
  169. package/template/components/home/sections/hero.tsx +187 -0
  170. package/template/components/home/sections/kits.tsx +11 -0
  171. package/template/components/home/sections/media-cards.tsx +70 -0
  172. package/template/components/home/sections/newsletter.tsx +62 -0
  173. package/template/components/home/sections/product-showcase.tsx +175 -0
  174. package/template/components/home/sections/purchase-hero.tsx +187 -0
  175. package/template/components/home/sections/quote-banner.tsx +50 -0
  176. package/template/components/home/sections/registry.ts +186 -0
  177. package/template/components/home/sections/reviews-carousel.tsx +66 -0
  178. package/template/components/home/sections/reviews.tsx +68 -0
  179. package/template/components/home/sections/ritual.tsx +96 -0
  180. package/template/components/home/sections/savings.tsx +47 -0
  181. package/template/components/home/sections/social-row.tsx +65 -0
  182. package/template/components/home/sections/spec-table.tsx +117 -0
  183. package/template/components/home/sections/stars.tsx +12 -0
  184. package/template/components/home/sections/stats-grid.tsx +54 -0
  185. package/template/components/home/sections/trust-bar.tsx +56 -0
  186. package/template/components/home/sections/trust-strip.tsx +40 -0
  187. package/template/components/home/sections/video-wall.tsx +59 -0
  188. package/template/components/landing/landing-recipe.ts +20 -0
  189. package/template/components/landing/oferta-sections.tsx +40 -0
  190. package/template/components/landing/product-picker.tsx +257 -0
  191. package/template/components/mobile-nav.tsx +71 -0
  192. package/template/components/order-status.tsx +163 -0
  193. package/template/components/powered-by-unbox.tsx +27 -0
  194. package/template/components/product/pdp/buy-box.tsx +604 -0
  195. package/template/components/product/pdp/catalog-grid.tsx +113 -0
  196. package/template/components/product/pdp/faq-modelo.ts +86 -0
  197. package/template/components/product/pdp/gallery.tsx +87 -0
  198. package/template/components/product/pdp/interactive.tsx +200 -0
  199. package/template/components/product/pdp/newsletter.tsx +64 -0
  200. package/template/components/product/pdp/payment-chips.tsx +22 -0
  201. package/template/components/product/pdp/pdp-view.tsx +227 -0
  202. package/template/components/product/pdp/recommendations.tsx +144 -0
  203. package/template/components/product/pdp/sections.tsx +173 -0
  204. package/template/components/quantity-stepper.tsx +36 -0
  205. package/template/components/search-box.tsx +37 -0
  206. package/template/components/site-footer.tsx +59 -0
  207. package/template/components/site-header.tsx +64 -0
  208. package/template/components/ui/accordion.tsx +72 -0
  209. package/template/components/ui/alert-dialog.tsx +187 -0
  210. package/template/components/ui/alert.tsx +76 -0
  211. package/template/components/ui/aspect-ratio.tsx +22 -0
  212. package/template/components/ui/avatar.tsx +109 -0
  213. package/template/components/ui/badge.tsx +52 -0
  214. package/template/components/ui/breadcrumb.tsx +125 -0
  215. package/template/components/ui/button.tsx +58 -0
  216. package/template/components/ui/card.tsx +103 -0
  217. package/template/components/ui/carousel.tsx +242 -0
  218. package/template/components/ui/command.tsx +196 -0
  219. package/template/components/ui/dialog.tsx +160 -0
  220. package/template/components/ui/dropdown-menu.tsx +268 -0
  221. package/template/components/ui/foto.tsx +54 -0
  222. package/template/components/ui/input-group.tsx +158 -0
  223. package/template/components/ui/input-otp.tsx +87 -0
  224. package/template/components/ui/input.tsx +20 -0
  225. package/template/components/ui/label.tsx +20 -0
  226. package/template/components/ui/navigation-menu.tsx +168 -0
  227. package/template/components/ui/pagination.tsx +132 -0
  228. package/template/components/ui/popover.tsx +90 -0
  229. package/template/components/ui/progress.tsx +83 -0
  230. package/template/components/ui/radio-group.tsx +38 -0
  231. package/template/components/ui/scroll-area.tsx +55 -0
  232. package/template/components/ui/select.tsx +201 -0
  233. package/template/components/ui/separator.tsx +25 -0
  234. package/template/components/ui/sheet.tsx +138 -0
  235. package/template/components/ui/skeleton.tsx +13 -0
  236. package/template/components/ui/sonner.tsx +44 -0
  237. package/template/components/ui/switch.tsx +32 -0
  238. package/template/components/ui/table.tsx +116 -0
  239. package/template/components/ui/tabs.tsx +82 -0
  240. package/template/components/ui/textarea.tsx +18 -0
  241. package/template/components/ui/toggle-group.tsx +89 -0
  242. package/template/components/ui/toggle.tsx +45 -0
  243. package/template/components/ui/tooltip.tsx +66 -0
  244. package/template/components.json +25 -0
  245. package/template/eslint.config.mjs +36 -0
  246. package/template/gitignore +21 -0
  247. package/template/lib/analytics.ts +369 -0
  248. package/template/lib/api.ts +60 -0
  249. package/template/lib/capi.ts +87 -0
  250. package/template/lib/cart-link.ts +51 -0
  251. package/template/lib/cart-normalize.ts +142 -0
  252. package/template/lib/cart-recovery.ts +23 -0
  253. package/template/lib/cart-response.ts +48 -0
  254. package/template/lib/catalog-map.ts +54 -0
  255. package/template/lib/catalog.ts +3 -0
  256. package/template/lib/checkout-lock.ts +21 -0
  257. package/template/lib/checkout-nav.ts +22 -0
  258. package/template/lib/config.ts +51 -0
  259. package/template/lib/crm.ts +40 -0
  260. package/template/lib/customer-session.ts +21 -0
  261. package/template/lib/dataloader.ts +23 -0
  262. package/template/lib/editable/config.ts +11 -0
  263. package/template/lib/editable/document.ts +1762 -0
  264. package/template/lib/editable/index.ts +3 -0
  265. package/template/lib/editable/primitives.tsx +821 -0
  266. package/template/lib/editable/provider.tsx +861 -0
  267. package/template/lib/editable/rastreio-navegacao.tsx +65 -0
  268. package/template/lib/editable/rastreio.tsx +168 -0
  269. package/template/lib/editable/server.ts +189 -0
  270. package/template/lib/editable/tokens.ts +22 -0
  271. package/template/lib/editable/verify.ts +24 -0
  272. package/template/lib/enrichment/combos.ts +184 -0
  273. package/template/lib/enrichment/index.ts +188 -0
  274. package/template/lib/enrichment/products.json +1 -0
  275. package/template/lib/env-check.ts +47 -0
  276. package/template/lib/format.ts +130 -0
  277. package/template/lib/icons.ts +67 -0
  278. package/template/lib/json-ld.ts +9 -0
  279. package/template/lib/llms-txt.ts +75 -0
  280. package/template/lib/mockup.ts +24 -0
  281. package/template/lib/newsletter.ts +12 -0
  282. package/template/lib/orders.ts +160 -0
  283. package/template/lib/queries.ts +66 -0
  284. package/template/lib/ratelimit.ts +47 -0
  285. package/template/lib/rotas-editaveis.ts +192 -0
  286. package/template/lib/sanitize.ts +36 -0
  287. package/template/lib/schemas.ts +78 -0
  288. package/template/lib/session.ts +98 -0
  289. package/template/lib/store-config.ts +67 -0
  290. package/template/lib/unbox/client.ts +829 -0
  291. package/template/lib/unbox/customer.ts +224 -0
  292. package/template/lib/unbox/errors.ts +97 -0
  293. package/template/lib/unbox/index.ts +10 -0
  294. package/template/lib/unbox/store.ts +113 -0
  295. package/template/lib/unbox/types.ts +195 -0
  296. package/template/lib/unbox/webhooks.ts +66 -0
  297. package/template/lib/utils.ts +6 -0
  298. package/template/lib/vitrine.ts +242 -0
  299. package/template/lib/webhook-store.ts +22 -0
  300. package/template/middleware.ts +178 -0
  301. package/template/next.config.ts +63 -0
  302. package/template/package.json +62 -0
  303. package/template/postcss.config.mjs +5 -0
  304. package/template/public/brand/coll/default.png +0 -0
  305. package/template/public/brand/hero-desktop.svg +12 -0
  306. package/template/public/brand/hero-mobile.svg +12 -0
  307. package/template/public/brand/heros/boutique-desktop.svg +11 -0
  308. package/template/public/brand/heros/boutique-mobile.svg +11 -0
  309. package/template/public/brand/heros/editorial-desktop.svg +11 -0
  310. package/template/public/brand/heros/editorial-mobile.svg +11 -0
  311. package/template/public/brand/heros/essencial-desktop.svg +12 -0
  312. package/template/public/brand/heros/essencial-mobile.svg +12 -0
  313. package/template/public/brand/heros/promocional-desktop.svg +12 -0
  314. package/template/public/brand/heros/promocional-mobile.svg +12 -0
  315. package/template/public/brand/logo-chrome.svg +6 -0
  316. package/template/public/brand/logo-white.svg +6 -0
  317. package/template/public/brand/logo.svg +7 -0
  318. package/template/public/brand/pay-amex.webp +0 -0
  319. package/template/public/brand/pay-elo.webp +0 -0
  320. package/template/public/brand/pay-mastercard.webp +0 -0
  321. package/template/public/brand/pay-pix.webp +0 -0
  322. package/template/public/brand/pay-visa.webp +0 -0
  323. package/template/public/brand/ph/avatar-a.svg +5 -0
  324. package/template/public/brand/ph/avatar-b.svg +5 -0
  325. package/template/public/brand/ph/avatar-c.svg +5 -0
  326. package/template/public/brand/ph/avatar-d.svg +5 -0
  327. package/template/public/brand/ph/photo-a.svg +7 -0
  328. package/template/public/brand/ph/photo-b.svg +6 -0
  329. package/template/public/brand/ph/photo-c.svg +6 -0
  330. package/template/public/brand/ph/poster-a.svg +6 -0
  331. package/template/public/brand/ph/poster-b.svg +6 -0
  332. package/template/public/brand/ph/poster-c.svg +6 -0
  333. package/template/public/unbox/powered-by-fundo-preto.png +0 -0
  334. package/template/public/unbox/powered-by-transparente.png +0 -0
  335. package/template/public/unbox/powered-by.png +0 -0
  336. package/template/scripts/abandoned-cart.ts +82 -0
  337. package/template/scripts/check-editable.mjs +467 -0
  338. package/template/scripts/check-honestidade.mjs +111 -0
  339. package/template/scripts/check-placeholder.mjs +200 -0
  340. package/template/scripts/check-recipe.mjs +76 -0
  341. package/template/scripts/check-unbox-brand.mjs +298 -0
  342. package/template/scripts/contraste.js +177 -0
  343. package/template/scripts/dump-catalog.ts +95 -0
  344. package/template/scripts/load-env.ts +13 -0
  345. package/template/scripts/medir-sistema.mjs +56 -0
  346. package/template/scripts/place-order-pix.ts +61 -0
  347. package/template/scripts/qa-screenshots.mjs +150 -0
  348. package/template/scripts/sistema.py +231 -0
  349. package/template/scripts/subscribe-webhook.ts +36 -0
  350. package/template/scripts/test-live.ts +186 -0
  351. package/template/scripts/vocabulario.py +264 -0
  352. package/template/tsconfig.json +27 -0
  353. package/tools/LEIA-ME.md +39 -0
  354. package/tools/check-template-neutro.mjs +111 -0
  355. package/tools/notion-doc.mjs +191 -0
  356. package/tools/tokenize-neutrals.mjs +0 -0
  357. package/tools/tokenize-radius.mjs +52 -0
  358. package/tools/workflow-storefront.legado.js +1207 -0
@@ -0,0 +1,64 @@
1
+ // HEADER "clássico" — logo à esquerda, busca no centro, ações à direita e a nav de
2
+ // categorias numa faixa abaixo. É o header de loja de catálogo: prioriza encontrar produto.
3
+ //
4
+ // Quando usar: catálogo grande, muitas categorias, busca é o caminho principal.
5
+ // Quando NÃO usar: marca com poucos SKUs que quer respiro (veja "imersivo"/"centralizado").
6
+ //
7
+ // EDITOR: o escopo (`chrome.header`) vem da casca (components/site-header.tsx). O logo é o único
8
+ // ponto de marca desta variante (`logo`, o mesmo caminho da barra do celular). Busca, conta e
9
+ // carrinho são INTERFACE; as categorias são DADO do catálogo da Unbox (nome e ordem vêm do painel):
10
+ // nada disso vira primitivo (README do editor, §8), e a nav de categorias sai do gate por
11
+ // `data-editor-ignore`.
12
+ import Link from "next/link";
13
+ import { BrandSearch } from "@/components/brand-search";
14
+ import { CartButton } from "@/components/cart-button";
15
+ import { AccountNav } from "@/components/account-nav";
16
+ import { EditableImg } from "@/lib/editable";
17
+ import { HeaderBarMobile } from "../header-bar-mobile";
18
+ import type { ChromeVariantProps } from "../registry";
19
+
20
+ export function HeaderClassico({ data }: ChromeVariantProps) {
21
+ const { shopName, categories } = data;
22
+
23
+ return (
24
+ <>
25
+ <HeaderBarMobile data={data} />
26
+
27
+ {/* desktop */}
28
+ <div className="mx-auto hidden max-w-[var(--container-max,1240px)] items-center gap-7 px-6 py-3.5 md:flex">
29
+ <Link href="/" aria-label={shopName} className="flex shrink-0">
30
+ {/* <img> puro (EditableImg), não next/image: logo do chrome é SVG de poucos KB, e o next/image
31
+ marcaria lazy num elemento que aparece em toda página (o preload scanner perde o recurso) e
32
+ o reencode come o traço fino do lettering. */}
33
+ <EditableImg path="logo" fallback={{ src: "/brand/logo.svg", alt: shopName }} label="Logo" className="h-[52px] w-auto" />
34
+ </Link>
35
+ <BrandSearch className="flex-1" />
36
+ <AccountNav />
37
+ <CartButton withLabel />
38
+ </div>
39
+
40
+ {/* nav de categorias (dado do catálogo: fora do editor) */}
41
+ <div className="hidden border-t border-[var(--store-surface-2)] md:block" data-editor-ignore="">
42
+ <div className="mx-auto flex max-w-[var(--container-max,1240px)] items-center gap-[26px] overflow-x-auto px-6">
43
+ <CatLink href="/produtos" label="Todos" />
44
+ {categories.map((c) => (
45
+ <CatLink key={c.id} href={`/categoria/${encodeURIComponent(c.slug)}`} label={c.name} />
46
+ ))}
47
+ </div>
48
+ </div>
49
+ </>
50
+ );
51
+ }
52
+
53
+ function CatLink({ href, label }: { href: string; label: string }) {
54
+ const isOffer = /oferta/i.test(label);
55
+ return (
56
+ <Link
57
+ href={href}
58
+ className="shrink-0 whitespace-nowrap border-b-[3px] border-transparent py-3 text-sm font-medium no-underline hover:border-[var(--store-primary,#18181B)] hover:text-[var(--store-primary,#18181B)]"
59
+ style={{ color: isOffer ? "var(--store-sale)" : "var(--store-ink-2)" }}
60
+ >
61
+ {label}
62
+ </Link>
63
+ );
64
+ }
@@ -0,0 +1,66 @@
1
+ // HEADER "compacto" — uma barra fina só: logo pequeno, categorias inline no MESMO eixo,
2
+ // busca em ícone e ações à direita. Densidade alta, pouca altura: sobra tela pra oferta.
3
+ //
4
+ // Quando usar: loja promocional//oferta, campanha, quem quer o produto o quanto antes.
5
+ // Quando NÃO usar: marca que precisa de respiro (veja "centralizado"/"imersivo").
6
+ //
7
+ // EDITOR: o escopo (`chrome.header`) vem da casca (components/site-header.tsx). O logo é o único
8
+ // ponto de marca desta variante (`logo`, o mesmo caminho da barra do celular). Busca, conta e
9
+ // carrinho são INTERFACE; as categorias são DADO do catálogo da Unbox: nada disso vira primitivo
10
+ // (README do editor, §8), e a nav de categorias sai do gate por `data-editor-ignore`.
11
+ import Link from "next/link";
12
+ import { MagnifyingGlass } from "@phosphor-icons/react/dist/ssr";
13
+ import { CartButton } from "@/components/cart-button";
14
+ import { AccountNav } from "@/components/account-nav";
15
+ import { EditableImg } from "@/lib/editable";
16
+ import { HeaderBarMobile } from "../header-bar-mobile";
17
+ import type { ChromeVariantProps } from "../registry";
18
+
19
+ export function HeaderCompacto({ data }: ChromeVariantProps) {
20
+ const { shopName, categories } = data;
21
+
22
+ return (
23
+ <>
24
+ <HeaderBarMobile data={data} />
25
+
26
+ {/* desktop — tudo numa linha */}
27
+ <div className="mx-auto hidden max-w-[var(--container-max,1240px)] items-center gap-5 px-6 py-2.5 md:flex">
28
+ <Link href="/" aria-label={shopName} className="flex shrink-0">
29
+ {/* <img> puro (EditableImg), não next/image: logo do chrome é SVG de poucos KB, e o next/image
30
+ marcaria lazy num elemento que aparece em toda página (o preload scanner perde o recurso) e
31
+ o reencode come o traço fino do lettering. */}
32
+ <EditableImg path="logo" fallback={{ src: "/brand/logo.svg", alt: shopName }} label="Logo" className="h-9 w-auto" />
33
+ </Link>
34
+
35
+ <nav className="flex min-w-0 flex-1 items-center gap-5 overflow-x-auto" aria-label="Categorias" data-editor-ignore="">
36
+ <NavLink href="/produtos" label="Todos" />
37
+ {/* barra fina: mostra as principais e deixa o resto no menu/catálogo */}
38
+ {categories.slice(0, 6).map((c) => (
39
+ <NavLink key={c.id} href={`/categoria/${encodeURIComponent(c.slug)}`} label={c.name} />
40
+ ))}
41
+ </nav>
42
+
43
+ <div className="flex shrink-0 items-center gap-4">
44
+ <Link href="/busca" aria-label="Buscar" className="flex text-[var(--store-ink-2)] transition-colors hover:text-[var(--store-primary,#18181B)]">
45
+ <MagnifyingGlass weight="bold" className="text-[21px]" />
46
+ </Link>
47
+ <AccountNav />
48
+ <CartButton withLabel />
49
+ </div>
50
+ </div>
51
+ </>
52
+ );
53
+ }
54
+
55
+ function NavLink({ href, label }: { href: string; label: string }) {
56
+ const isOffer = /oferta/i.test(label);
57
+ return (
58
+ <Link
59
+ href={href}
60
+ className="shrink-0 whitespace-nowrap text-[13.5px] font-semibold uppercase tracking-[0.4px] no-underline transition-colors hover:text-[var(--store-primary,#18181B)]"
61
+ style={{ color: isOffer ? "var(--store-sale)" : "var(--store-ink-2)" }}
62
+ >
63
+ {label}
64
+ </Link>
65
+ );
66
+ }
@@ -0,0 +1,73 @@
1
+ // HEADER "equilibrado" — uma barra só, em três pesos: categorias à ESQUERDA, logo ao
2
+ // CENTRO e as três ações (busca, conta, carrinho) em ícone à DIREITA.
3
+ //
4
+ // É o meio-termo entre o "compacto" (tudo à esquerda, denso) e o "centralizado" (duas
5
+ // linhas, editorial): mantém o logo como eixo da página sem gastar uma segunda faixa,
6
+ // e as ações viram ícone puro pra não competir com a marca.
7
+ //
8
+ // Quando usar: catálogo curto/médio (2 a 6 categorias cabem na esquerda), marca que quer
9
+ // o logo no centro mas sem o peso de duas linhas.
10
+ // Quando NÃO usar: muitas categorias (elas empurram o logo pra fora do centro — aí use
11
+ // "classico" ou "compacto").
12
+ //
13
+ // EDITOR: o escopo (`chrome.header`) vem da casca (components/site-header.tsx). O logo é o único
14
+ // ponto de marca desta variante (`logo`, o mesmo caminho da barra do celular). Busca, conta e
15
+ // carrinho são INTERFACE; as categorias são DADO do catálogo da Unbox: nada disso vira primitivo
16
+ // (README do editor, §8), e a nav de categorias sai do gate por `data-editor-ignore`.
17
+ import Link from "next/link";
18
+ import { MagnifyingGlass } from "@phosphor-icons/react/dist/ssr";
19
+ import { CartButton } from "@/components/cart-button";
20
+ import { AccountNav } from "@/components/account-nav";
21
+ import { EditableImg } from "@/lib/editable";
22
+ import { HeaderBarMobile } from "../header-bar-mobile";
23
+ import type { ChromeVariantProps } from "../registry";
24
+
25
+ export function HeaderEquilibrado({ data }: ChromeVariantProps) {
26
+ const { shopName, categories } = data;
27
+
28
+ return (
29
+ <>
30
+ <HeaderBarMobile data={data} />
31
+
32
+ {/* desktop — grid de 3 colunas iguais mantém o logo no centro ÓTICO da barra,
33
+ independente de quantas categorias existem à esquerda. */}
34
+ <div className="mx-auto hidden max-w-[var(--container-max,1240px)] grid-cols-[1fr_auto_1fr] items-center gap-6 px-6 py-3 md:grid">
35
+ <nav className="flex min-w-0 items-center gap-6 overflow-x-auto justify-self-start" aria-label="Categorias" data-editor-ignore="">
36
+ <NavLink href="/produtos" label="Todos" />
37
+ {/* barra de uma linha: as principais aqui, o resto no catálogo */}
38
+ {categories.slice(0, 4).map((c) => (
39
+ <NavLink key={c.id} href={`/categoria/${encodeURIComponent(c.slug)}`} label={c.name} />
40
+ ))}
41
+ </nav>
42
+
43
+ <Link href="/" aria-label={shopName} className="flex justify-self-center">
44
+ {/* <img> puro (EditableImg), não next/image: logo do chrome é SVG de poucos KB, e o next/image
45
+ marcaria lazy num elemento que aparece em toda página (o preload scanner perde o recurso) e
46
+ o reencode come o traço fino do lettering. */}
47
+ <EditableImg path="logo" fallback={{ src: "/brand/logo.svg", alt: shopName }} label="Logo" className="h-[46px] w-auto" />
48
+ </Link>
49
+
50
+ <div className="flex items-center gap-5 justify-self-end">
51
+ <Link href="/busca" aria-label="Buscar" className="flex text-[var(--store-ink)] transition-colors hover:text-[var(--store-primary,#18181B)]">
52
+ <MagnifyingGlass className="text-[22px]" />
53
+ </Link>
54
+ <AccountNav iconOnly />
55
+ <CartButton />
56
+ </div>
57
+ </div>
58
+ </>
59
+ );
60
+ }
61
+
62
+ function NavLink({ href, label }: { href: string; label: string }) {
63
+ const isOffer = /oferta/i.test(label);
64
+ return (
65
+ <Link
66
+ href={href}
67
+ className="shrink-0 whitespace-nowrap text-[13px] font-semibold uppercase tracking-[0.6px] no-underline transition-colors hover:text-[var(--store-primary,#18181B)]"
68
+ style={{ color: isOffer ? "var(--store-sale)" : "var(--store-ink-2)" }}
69
+ >
70
+ {label}
71
+ </Link>
72
+ );
73
+ }
@@ -0,0 +1,62 @@
1
+ // HEADER "imersivo" — barra mínima de altura fixa: menu em drawer (também no desktop),
2
+ // logo centralizado, carrinho e conta discretos. Muito respiro, nenhuma faixa de anúncio.
3
+ //
4
+ // O diferencial: quando a página abre com um hero de imagem full-bleed
5
+ // (variante `imagem-imersiva` da seção hero), o header fica TRANSPARENTE sobre a foto e vira
6
+ // sólido depois de ~80px de rolagem. Em catálogo, PDP, conta e busca ele é sólido desde o
7
+ // topo — a regra vive em app/globals.css e depende de o hero marcar `.hero-imersivo`, então
8
+ // a degradação é automática: sem foto, header sólido.
9
+ //
10
+ // Quando usar: marca com fotografia boa, catálogo curto, posicionamento premium.
11
+ // Quando NÃO usar: catálogo grande (a navegação fica toda no drawer) ou loja sem foto real.
12
+ //
13
+ // EDITOR: o escopo (`chrome.header`) vem da casca (components/site-header.tsx). O logo é o único
14
+ // ponto de marca desta variante (`logo`, o mesmo caminho da barra do celular: aqui as duas usam
15
+ // logo-chrome.svg, o legível sobre a cor do chrome). Menu, busca, conta e carrinho são INTERFACE:
16
+ // ficam fora dos primitivos (README do editor, §8).
17
+ import Link from "next/link";
18
+ import { MagnifyingGlass } from "@phosphor-icons/react/dist/ssr";
19
+ import { CartButton } from "@/components/cart-button";
20
+ import { AccountNav } from "@/components/account-nav";
21
+ import { MobileNav } from "@/components/mobile-nav";
22
+ import { EditableImg } from "@/lib/editable";
23
+ import { SolidOnScroll } from "../solid-on-scroll";
24
+ import { HeaderBarMobile } from "../header-bar-mobile";
25
+ import type { ChromeVariantProps } from "../registry";
26
+
27
+ export function HeaderImersivo({ data }: ChromeVariantProps) {
28
+ const { shopName, categories } = data;
29
+
30
+ return (
31
+ // .chrome-bar + data-solid: a pintura (transparente ↔ sólido) é decidida no CSS.
32
+ <SolidOnScroll className="bg-[var(--store-chrome-bg,#18181B)] text-[var(--store-chrome-text,#ffffff)] transition-colors duration-200">
33
+ <HeaderBarMobile data={data} tone="over" />
34
+
35
+ {/* desktop — altura travada em --chrome-h: se crescer, o hero sobe errado */}
36
+ <div className="mx-auto hidden h-[var(--chrome-h,76px)] max-w-[var(--container-max,1240px)] grid-cols-[1fr_auto_1fr] items-center gap-6 px-6 md:grid">
37
+ <div className="justify-self-start">
38
+ <MobileNav
39
+ categories={categories}
40
+ shopName={shopName}
41
+ triggerClassName="text-[var(--store-chrome-text,#ffffff)]"
42
+ />
43
+ </div>
44
+
45
+ <Link href="/" aria-label={shopName} className="flex justify-self-center">
46
+ {/* logo-chrome.svg = o logo que o CLI garante legível sobre a cor de chrome — e o
47
+ estado transparente usa um véu NA COR do chrome, então vale nos dois estados.
48
+ <img> puro (EditableImg), não next/image: SVG de poucos KB em toda página. */}
49
+ <EditableImg path="logo" fallback={{ src: "/brand/logo-chrome.svg", alt: shopName }} label="Logo" className="max-h-[calc(var(--chrome-h,76px)-28px)] w-auto" />
50
+ </Link>
51
+
52
+ <div className="flex items-center gap-5 justify-self-end">
53
+ <Link href="/busca" aria-label="Buscar" className="flex text-[var(--store-chrome-text,#ffffff)] opacity-90 transition-opacity hover:opacity-100">
54
+ <MagnifyingGlass weight="bold" className="text-[21px]" />
55
+ </Link>
56
+ <AccountNav tone="chrome" />
57
+ <CartButton tone="chrome" />
58
+ </div>
59
+ </div>
60
+ </SolidOnScroll>
61
+ );
62
+ }
@@ -0,0 +1,70 @@
1
+ // Registry do CHROME (header + rodapé) — o mapa nome→componente que a receita
2
+ // (components/chrome/chrome-recipe.ts) referencia. Mesma mecânica do registry das seções
3
+ // da home. Pra criar uma variante nova: arquivo em headers/ ou footers/ + entrada aqui +
4
+ // documentar em agents/PADROES.md.
5
+ //
6
+ // ⚠️ Sem "use client": header e rodapé são SERVER components (a casca faz o fetch). As
7
+ // variantes compõem as peças client que já existem (BrandSearch, CartButton, AccountNav...).
8
+ //
9
+ // ⚠️ A variante desenha só o MIOLO. O elemento raiz (<header>/<footer>), com as classes
10
+ // obrigatórias `store-layout site-chrome`, é da casca (site-header.tsx/site-footer.tsx) —
11
+ // é `.site-chrome` que faz o chrome sumir no checkout (app/globals.css). Assim uma variante
12
+ // nova não tem como quebrar isso.
13
+ import type { NavCategory } from "@/components/mobile-nav";
14
+ import { HeaderClassico } from "./headers/classico";
15
+ import { HeaderCompacto } from "./headers/compacto";
16
+ import { HeaderCentralizado } from "./headers/centralizado";
17
+ import { HeaderEquilibrado } from "./headers/equilibrado";
18
+ import { HeaderImersivo } from "./headers/imersivo";
19
+ import { FooterColunas } from "./footers/colunas";
20
+ import { FooterConversao } from "./footers/conversao";
21
+ import { FooterEditorial } from "./footers/editorial";
22
+ import { FooterMinimal } from "./footers/minimal";
23
+
24
+ /** Dados que a casca busca UMA vez e entrega à variante (evita fetch duplicado). */
25
+ export interface ChromeData {
26
+ shopName: string;
27
+ categories: NavCategory[];
28
+ }
29
+
30
+ /** Props que toda variante de header/rodapé recebe da casca. */
31
+ export interface ChromeVariantProps {
32
+ data: ChromeData;
33
+ }
34
+
35
+ export const HEADERS = {
36
+ classico: HeaderClassico,
37
+ compacto: HeaderCompacto,
38
+ centralizado: HeaderCentralizado,
39
+ equilibrado: HeaderEquilibrado,
40
+ imersivo: HeaderImersivo,
41
+ } as const;
42
+
43
+ export const FOOTERS = {
44
+ colunas: FooterColunas,
45
+ conversao: FooterConversao,
46
+ editorial: FooterEditorial,
47
+ minimal: FooterMinimal,
48
+ } as const;
49
+
50
+ export type HeaderName = keyof typeof HEADERS;
51
+ export type FooterName = keyof typeof FOOTERS;
52
+
53
+ /** Como a casca desenha o <header>:
54
+ * - "solido": sticky e opaco desde o topo (o padrão).
55
+ * - "sobreposto": transparente sobre um hero de imagem full-bleed, sólido ao rolar.
56
+ * O Record obriga o TypeScript a cobrir toda variante nova. */
57
+ export type HeaderShellMode = "solido" | "sobreposto";
58
+ export const HEADER_SHELL: Record<HeaderName, HeaderShellMode> = {
59
+ classico: "solido",
60
+ compacto: "solido",
61
+ centralizado: "solido",
62
+ equilibrado: "solido",
63
+ imersivo: "sobreposto",
64
+ };
65
+
66
+ /** A receita do chrome: qual header e qual rodapé esta loja usa. */
67
+ export interface ChromeRecipe {
68
+ header: HeaderName;
69
+ footer: FooterName;
70
+ }
@@ -0,0 +1,39 @@
1
+ "use client";
2
+
3
+ // Casca de pintura do header SOBREPOSTO: transparente no topo, sólido depois de rolar.
4
+ // É o ÚNICO arquivo client do chrome — as variantes seguem sendo server components e
5
+ // entram aqui como `children` (o App Router renderiza o filho no servidor e serializa).
6
+ //
7
+ // Sem hydration mismatch: o estado inicial (`false`) é exatamente o que o SSR emitiu.
8
+ // Nunca leia window.scrollY durante o render.
9
+ //
10
+ // A decisão de PINTURA fica toda no CSS (`.chrome-bar:not([data-solid])` em globals.css),
11
+ // por isso aqui só marcamos o atributo — assim o estado de scroll e a regra de
12
+ // transparência condicional (:has(.hero-imersivo)) convivem sem duplicar cor no JSX.
13
+ import * as React from "react";
14
+
15
+ export function SolidOnScroll({
16
+ children,
17
+ className = "",
18
+ threshold = 80,
19
+ }: {
20
+ children: React.ReactNode;
21
+ className?: string;
22
+ threshold?: number;
23
+ }) {
24
+ const [solid, setSolid] = React.useState(false);
25
+
26
+ React.useEffect(() => {
27
+ const onScroll = () =>
28
+ setSolid((window.scrollY || document.documentElement.scrollTop || 0) > threshold);
29
+ window.addEventListener("scroll", onScroll, { passive: true });
30
+ onScroll(); // corrige scroll restaurado (voltar/avançar, link com #hash)
31
+ return () => window.removeEventListener("scroll", onScroll);
32
+ }, [threshold]);
33
+
34
+ return (
35
+ <div className={`chrome-bar ${className}`} data-solid={solid || undefined}>
36
+ {children}
37
+ </div>
38
+ );
39
+ }
@@ -0,0 +1,22 @@
1
+ import { Package } from "@phosphor-icons/react/dist/ssr";
2
+
3
+ export function EmptyState({
4
+ title,
5
+ description,
6
+ children,
7
+ }: {
8
+ title: string;
9
+ description?: string;
10
+ children?: React.ReactNode;
11
+ }) {
12
+ return (
13
+ <div className="flex flex-col items-center justify-center rounded-xl border border-dashed border-[var(--store-line-2)] bg-[var(--store-surface)] px-6 py-16 text-center">
14
+ <span className="mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-[var(--store-primary-soft,#F1F1F3)] text-[var(--store-primary,#18181B)]">
15
+ <Package weight="duotone" className="text-[34px]" />
16
+ </span>
17
+ <h2 className="font-display text-[18px] font-extrabold text-[var(--store-ink)]">{title}</h2>
18
+ {description && <p className="mt-1.5 max-w-sm text-[14px] leading-[1.5] text-[var(--store-muted)]">{description}</p>}
19
+ {children && <div className="mt-5">{children}</div>}
20
+ </div>
21
+ );
22
+ }
@@ -0,0 +1,67 @@
1
+ "use client";
2
+
3
+ // Card de combo compacto — usado no mobile (home) e no catálogo. Autocontido (add-to-cart).
4
+ import * as React from "react";
5
+ import Image from "next/image";
6
+ import { ShoppingCartSimple, Spinner, PiggyBank } from "@phosphor-icons/react/dist/ssr";
7
+ import { useCart } from "@/components/cart/cart-provider";
8
+ import { formatBRL } from "@/lib/format";
9
+ import type { ResolvedCombo } from "@/lib/enrichment/combos";
10
+
11
+ export function ComboCardCompact({ combo, className = "" }: { combo: ResolvedCombo; className?: string }) {
12
+ const { add, setOpen } = useCart();
13
+ const [busy, setBusy] = React.useState(false);
14
+ const hero = combo.imageUrl || combo.items[0]?.imageUrl || null;
15
+
16
+ async function addCombo() {
17
+ setBusy(true);
18
+ try {
19
+ for (const it of combo.items) {
20
+ await add(
21
+ { productId: it.productId, variantId: it.variantId, price: it.discountedPrice, quantity: it.qty, thumbnail: it.imageUrl ?? undefined, title: `${combo.name} · ${it.name}` },
22
+ { silent: true },
23
+ );
24
+ }
25
+ setOpen(true);
26
+ } finally {
27
+ setBusy(false);
28
+ }
29
+ }
30
+
31
+ return (
32
+ <div className={`flex flex-col overflow-hidden rounded-2xl border border-[var(--store-line)] bg-white ${className}`}>
33
+ <div className="relative aspect-[4/3] bg-[var(--store-surface)]">
34
+ {combo.badge && (
35
+ <span className="absolute left-2.5 top-2.5 z-[2] rounded-full bg-[var(--store-cta-soft)] px-2.5 py-0.5 text-[10px] font-extrabold tracking-[0.3px] text-[var(--store-cta-fg)]">{combo.badge}</span>
36
+ )}
37
+ {hero && <Image src={hero} alt={combo.name} fill sizes="240px" className="object-cover" />}
38
+ </div>
39
+
40
+ <div className="flex flex-1 flex-col p-3.5">
41
+ <h3 className="font-display text-[16px] font-extrabold leading-[1.12] text-[var(--store-ink)]">{combo.name}</h3>
42
+ <p className="mt-0.5 text-[12px] text-[var(--store-muted)]">{combo.itemCount} itens</p>
43
+
44
+ {combo.save > 0 && (
45
+ <div className="mt-2.5 flex items-center gap-2">
46
+ <span className="text-[12.5px] text-[var(--store-faint)] line-through">{formatBRL(combo.subtotal)}</span>
47
+ <span className="rounded bg-[var(--store-cta,#D97706)] px-1.5 py-0.5 text-[10px] font-extrabold text-[var(--store-cta-fg,#1C1207)]">{combo.discountPct}% OFF</span>
48
+ </div>
49
+ )}
50
+ <div className="font-display text-[22px] font-extrabold leading-[1.1] text-[var(--store-primary,#18181B)]">{formatBRL(combo.total)}</div>
51
+ {combo.save > 0 && (
52
+ <div className="mt-0.5 flex items-center gap-1 text-[12px] font-bold text-[var(--store-primary,#18181B)]"><PiggyBank weight="fill" />Economize {formatBRL(combo.save)}</div>
53
+ )}
54
+
55
+ <button
56
+ type="button"
57
+ onClick={addCombo}
58
+ disabled={busy}
59
+ className="font-display mt-3 flex h-11 w-full cursor-pointer items-center justify-center gap-2 rounded-xl bg-[var(--store-primary,#18181B)] text-[13.5px] font-bold text-white transition-colors hover:bg-[var(--store-primary-dark,#09090B)] disabled:opacity-60"
60
+ >
61
+ {busy ? <Spinner className="animate-spin text-base" /> : <ShoppingCartSimple weight="bold" className="text-base" />}
62
+ Adicionar
63
+ </button>
64
+ </div>
65
+ </div>
66
+ );
67
+ }
@@ -0,0 +1,64 @@
1
+ "use client";
2
+
3
+ // Home = renderer da RECEITA (components/home/home-recipe.ts): mapeia cada entrada da
4
+ // receita pro componente registrado em sections/registry.ts. Pra mudar o layout da home
5
+ // (ordem, variantes, props), edite a RECEITA — não este arquivo nem o JSX das seções.
6
+ import { homeRecipe } from "@/components/home/home-recipe";
7
+ import { SECTIONS, withIds, type HomeData } from "@/components/home/sections/registry";
8
+ import { catalogoDaHome } from "@/components/home/sections/catalogo";
9
+ import { Editable } from "@/lib/editable";
10
+ import type { CatalogProductItem, CatalogCategory } from "@/components/catalog/catalog-client";
11
+ import type { ResolvedCombo } from "@/lib/enrichment/combos";
12
+ // só o TIPO (lib/vitrine.ts é `server-only`; `import type` some na compilação)
13
+ import type { VitrinesResolvidas } from "@/lib/vitrine";
14
+
15
+ export function CombosHome({
16
+ combos,
17
+ featured,
18
+ bundles = [],
19
+ categories = [],
20
+ combosTitle,
21
+ freeShipLabel,
22
+ vitrines,
23
+ layoutEditavel = true,
24
+ }: {
25
+ combos: CatalogProductItem[];
26
+ featured: CatalogProductItem | null;
27
+ bundles?: ResolvedCombo[];
28
+ categories?: CatalogCategory[];
29
+ combosTitle: string;
30
+ freeShipLabel: string | null;
31
+ /**
32
+ * Vitrines que o SERVIDOR já resolveu, por caminho. Omitido = nenhuma escolha do lojista valeu
33
+ * (ou a página nem leu o documento): toda vitrine mostra os produtos do código.
34
+ */
35
+ vitrines?: VitrinesResolvidas;
36
+ /** `false` em página que REAPROVEITA a receita: a copy editada vale, a ordem da home não manda ali. */
37
+ layoutEditavel?: boolean;
38
+ }) {
39
+ const data: HomeData = { combos, featured, bundles, categories, combosTitle, freeShipLabel, vitrines };
40
+ return (
41
+ <div className="store-layout full-bleed bg-white text-[var(--store-ink)]">
42
+ {/* O título do hero é o h1 da home. Sem hero na receita, um h1 invisível com o nome da loja. */}
43
+ {!homeRecipe.some((e) => e.section === "hero") && <h1 className="sr-only">{process.env.NEXT_PUBLIC_SITE_NAME ?? "Loja"}</h1>}
44
+ {/* EDITOR: cada entrada da receita vira seção com id estável (withIds: o que a receita declara,
45
+ senão IDENTIDADE_PADRAO do registry); o container aplica a ordem e as ocultas que o lojista
46
+ publicou. Sem documento, renderiza a receita na ordem do código.
47
+ `catalogo` = os tipos que ESTA loja sabe instanciar quando o lojista clica em "adicionar
48
+ seção" (components/home/sections/catalogo.ts). É o único lugar onde essa lista existe: o
49
+ editor não tem catálogo próprio. Não precisa de guarda por `layoutEditavel`: o próprio
50
+ container ignora catálogo e seções criadas quando `layout` é false (uma página que reusa
51
+ este container só para reaproveitar componentes não pode ganhar seção nova). */}
52
+ <Editable.Sections container="home" layout={layoutEditavel} catalogo={catalogoDaHome(data)}>
53
+ {withIds(homeRecipe).map((entry) => {
54
+ const Section = SECTIONS[entry.section];
55
+ return (
56
+ <Editable.Section key={entry.id} id={entry.id} kind={entry.kind} label={entry.label}>
57
+ <Section data={data} variant={entry.variant} sectionProps={entry.props} />
58
+ </Editable.Section>
59
+ );
60
+ })}
61
+ </Editable.Sections>
62
+ </div>
63
+ );
64
+ }