@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,165 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════
2
+ // CATÁLOGO DE SEÇÕES QUE O LOJISTA PODE ADICIONAR ("+ Adicionar seção" do editor)
3
+ //
4
+ // Quem declara o que pode ser adicionado é a LOJA, nunca o editor. O editor não tem, e não pode ter,
5
+ // lista própria: são 100 lojas, 100 catálogos. O que existe aqui não é um catálogo de EDITABILIDADE
6
+ // (isso continua sendo as "4 latas" da foundation); é o catálogo de COMPONENTES desta loja, que já
7
+ // mora em `registry.ts`, recortado na fatia que o lojista consegue usar sozinho.
8
+ //
9
+ // ── O CORTE: 8 dos 24 do registry ─────────────────────────────────────────────
10
+ // Um tipo só entra se, instanciado SEM props da receita, ele:
11
+ // 1. RENDERIZA algo (não cai em `return null` por lista vazia), e
12
+ // 2. o que ele renderiza é EDITÁVEL: a copy nasce dentro de primitivo (`Editable.Text` &cia.).
13
+ // A regra 1 é a que mais corta nesta foundation: por honestidade, várias seções só renderizam com dado
14
+ // da receita (depoimentos reais, vídeo real, ficha do produto, atributos da marca, promessa de
15
+ // newsletter com endereço de envio). Adicionar uma delas pelo "+" entregaria ao lojista uma seção que
16
+ // não aparece em lugar nenhum. Pior que não oferecer.
17
+ //
18
+ // ── A EXCEÇÃO DA REGRA 1: o bloco de HTML ─────────────────────────────────────
19
+ // "bloco-html" é a única entrada que, recém-adicionada, renderiza NADA na loja publicada, e isso é o
20
+ // desenho, não o modo de falha que a regra 1 persegue. As seções cortadas somem porque falta DADO que
21
+ // o lojista não tem como fornecer; esta nasce vazia porque o conteúdo dela é justamente o que ele vai
22
+ // colar, e no editor (o único lugar onde ele acabou de clicar no "+") ela SEMPRE aparece, com o convite
23
+ // para colar. Sumir da loja enquanto está vazia é o certo.
24
+ //
25
+ // ── PLACEHOLDER NA SEÇÃO CRIADA ───────────────────────────────────────────────
26
+ // Benefícios, como funciona, cards de imagem e citação nascem com a arte de /brand/ph (o SVG que o
27
+ // create-unbox-store recolore com a paleta da marca) e com a copy neutra do componente. É a mesma arte
28
+ // e a mesma copy com que a home da foundation já nasce: o lojista clica na foto e sobe a dele. Se uma
29
+ // loja decidir que o "+" não pode entregar placeholder, tire a entrada daqui; nada mais muda.
30
+ //
31
+ // ── `tipo` É CHAVE PRIMÁRIA (não renomeie) ────────────────────────────────────
32
+ // A chave de cada entrada é o que vai para o documento (`sections.home.criadas: { id → tipo }`) e para
33
+ // o id da seção (`novo-<tipo>-<n>`). Renomear um `tipo` depois que alguém publicou: as seções já
34
+ // adicionadas apontam para um tipo que o catálogo não tem mais, `Editable.Sections` as filtra fora e
35
+ // elas SOMEM DA TELA em silêncio (o painel continua listando, porque ele deriva do rascunho). Tratar
36
+ // como id de seção da receita: nome em pt-br, escolhido uma vez, nunca mais mexido.
37
+ //
38
+ // ── `variant` sim, `props` não ────────────────────────────────────────────────
39
+ // A seção criada nasce com os LITERAIS do componente: nada é escrito em `values`. Por isso este
40
+ // arquivo não passa `sectionProps`: copy que morasse aqui viveria em dois lugares (receita e
41
+ // catálogo) e divergiria, e é justamente a copy que o lojista vai trocar no primeiro clique.
42
+ // `variant` é a exceção porque não é conteúdo, é QUAL composição o componente renderiza.
43
+ //
44
+ // ── FICARAM DE FORA (motivo curto, para quem for mexer depois) ────────────────
45
+ // attributes-marquee, trust-bar, trust-strip, spec-table, video-wall, reviews, reviews-carousel,
46
+ // comparison, founder-story, newsletter ..... `return null` sem dado da receita (regra 1). É
47
+ // decisão de honestidade da foundation 0.19, e vale igual no "+".
48
+ // category-pills, combos-carousel, savings, kits, purchase-hero ..... dependem do catálogo/da
49
+ // configuração comercial; somem ou esvaziam sem eles.
50
+ // social-row ..... renderiza com defaults, mas os defaults são gente placeholder ("@suamarca"), e a
51
+ // regra da seção é GENTE REAL com autorização: não entra pelo "+".
52
+ // stats-grid ..... renderiza e é editável, mas é a seção de NÚMERO com dado real; no "+" ela nasceria
53
+ // como quatro cards qualitativos e convidaria a inventar estatística.
54
+ //
55
+ // JSX não entra aqui porque o arquivo é `.ts` (o catálogo é declaração, não tela): a instância sai de
56
+ // `React.createElement`.
57
+ // ═══════════════════════════════════════════════════════════════════════════
58
+ import * as React from "react";
59
+ import type { CatalogoDeSecoes, SectionKind } from "@/lib/editable";
60
+ import { SECTIONS, type HomeData, type SectionComponentProps, type SectionName } from "./registry";
61
+
62
+ interface EntradaDoCatalogo {
63
+ /** chave do catálogo: vira `novo-<tipo>-<n>` e o valor de `criadas`. NÃO RENOMEAR (ver topo). */
64
+ tipo: string;
65
+ /** componente do registry que instancia este tipo */
66
+ section: SectionName;
67
+ /** composição do componente; omitida = a default dele */
68
+ variant?: string;
69
+ /** o nome que o lojista lê no "+", nunca o nome de código */
70
+ label: string;
71
+ /** uma frase dizendo o que a seção mostra (o v1 não tem imagem de prévia) */
72
+ descricao: string;
73
+ /** tipo no vocabulário fechado da foundation (`SECTION_KINDS`) */
74
+ kind: SectionKind;
75
+ }
76
+
77
+ const CATALOGO_DA_HOME: EntradaDoCatalogo[] = [
78
+ {
79
+ tipo: "banner",
80
+ section: "hero",
81
+ // "imagem-full" é a única variante do banner que não escreve texto na tela: nasce com a arte da
82
+ // foundation, e o lojista sobe a imagem dele (uma para computador, outra para celular).
83
+ variant: "imagem-full",
84
+ label: "Banner",
85
+ descricao: "Uma imagem larga, com versão para computador e para celular, que leva aos destaques da loja.",
86
+ kind: "banner",
87
+ },
88
+ {
89
+ // A VITRINE. Cada uma adicionada ganha id próprio (`novo-vitrine-de-produtos-1`, `-2`, …), e o
90
+ // caminho da escolha sai desse id (`home.<id>.vitrine`): DUAS vitrines na mesma página têm DUAS
91
+ // escolhas independentes, sem uma pisar na outra.
92
+ tipo: "vitrine-de-produtos",
93
+ section: "product-showcase",
94
+ label: "Vitrine de produtos",
95
+ descricao: "Uma grade de produtos que você escolhe (uma categoria inteira, uma lista sua ou uma busca), com foto, preço e botão de comprar em cada card.",
96
+ kind: "vitrine-de-produtos",
97
+ },
98
+ {
99
+ tipo: "beneficios",
100
+ section: "benefits",
101
+ label: "Benefícios",
102
+ descricao: "Título, duas colunas de benefícios com texto curto e uma foto no meio.",
103
+ kind: "beneficios",
104
+ },
105
+ {
106
+ tipo: "como-funciona",
107
+ section: "ritual",
108
+ label: "Como funciona",
109
+ descricao: "Fotos que se alternam de um lado e, do outro, um selo, um título, uma lista de passos e um botão.",
110
+ kind: "como-funciona",
111
+ },
112
+ {
113
+ tipo: "cards-de-imagem",
114
+ section: "media-cards",
115
+ label: "Cards de imagem",
116
+ descricao: "Título e uma fileira de cards com foto e legenda, que rolam para o lado.",
117
+ kind: "galeria",
118
+ },
119
+ {
120
+ tipo: "citacao",
121
+ section: "quote-banner",
122
+ label: "Citação",
123
+ descricao: "Faixa de cor da marca com uma foto e uma frase grande assinada por alguém da loja.",
124
+ kind: "sobre-a-marca",
125
+ },
126
+ {
127
+ // O BLOCO DE HTML: a única entrada que não desenha nada por conta própria (ver "A EXCEÇÃO DA
128
+ // REGRA 1" no topo). `kind: "outro"` é a escolha honesta: o vocabulário fechado da foundation não
129
+ // tem um tipo para "o que o lojista colar", e carimbar "beneficios" ou "sobre-a-marca" faria o
130
+ // painel afirmar um conteúdo que ninguém conferiu.
131
+ tipo: "bloco-html",
132
+ section: "bloco-html",
133
+ label: "Bloco de HTML",
134
+ // A frase diz o que NÃO entra porque a recusa acontece depois de colar: sem esse aviso aqui, o
135
+ // lojista descobre a regra levando um "não" com o texto já no campo.
136
+ descricao: "Para colar um código pronto que te passaram: um selo, uma tabela de medidas, um trecho de conteúdo. Código que roda (script) e formulário não entram.",
137
+ kind: "outro",
138
+ },
139
+ ];
140
+
141
+ /**
142
+ * O catálogo pronto para `Editable.Sections catalogo={…}`.
143
+ *
144
+ * Recebe `data` porque é o que a home já buscou no servidor: `render` fecha por closure e as seções
145
+ * criadas enxergam o mesmo catálogo de produtos das que vêm do código. Pode ser chamado a cada render:
146
+ * `Editable.Sections` compara o catálogo por uma ASSINATURA de texto, não por referência, então um
147
+ * objeto novo por render não re-registra os tipos nem impede o manifesto de assentar.
148
+ */
149
+ export function catalogoDaHome(data: HomeData): CatalogoDeSecoes {
150
+ return Object.fromEntries(
151
+ CATALOGO_DA_HOME.map((e) => {
152
+ // o componente do registry é um union de todas as seções; todas recebem `SectionComponentProps`
153
+ const Componente = SECTIONS[e.section] as React.ComponentType<SectionComponentProps>;
154
+ return [
155
+ e.tipo,
156
+ {
157
+ label: e.label,
158
+ descricao: e.descricao,
159
+ kind: e.kind,
160
+ render: () => React.createElement(Componente, { data, variant: e.variant }),
161
+ },
162
+ ];
163
+ }),
164
+ );
165
+ }
@@ -0,0 +1,32 @@
1
+ "use client";
2
+
3
+ // PILLS de categorias — chips com thumb, quebram em várias linhas pra exibir todas.
4
+ //
5
+ // EDITOR: nada aqui é copy da marca. Nome, ordem e imagem das categorias vêm do catálogo da Unbox
6
+ // (o lojista muda no painel da plataforma), então a seção não abre ponto editável e sai do gate por
7
+ // `data-editor-ignore` (README do editor, §8: produto do catálogo não vira primitivo). O que o
8
+ // lojista edita nela é o lugar na página: ordem e ocultar, pela seção.
9
+ import Link from "next/link";
10
+ import Image from "next/image";
11
+ import { collImageSrc } from "@/components/catalog/catalog-client";
12
+ import type { SectionComponentProps } from "./registry";
13
+
14
+ export function CategoryPillsSection({ data }: SectionComponentProps) {
15
+ if (data.categories.length === 0) return null;
16
+ return (
17
+ <div className="mx-auto max-w-[var(--container-max,1240px)] px-4 pt-[18px] sm:px-6">
18
+ <div className="flex flex-wrap gap-2.5" data-editor-ignore="">
19
+ {data.categories.map((c) => (
20
+ <Link
21
+ key={c.slug}
22
+ href={`/categoria/${encodeURIComponent(c.slug)}`}
23
+ className="flex flex-none items-center gap-[11px] rounded-full border-[1.5px] border-[var(--store-line)] bg-white py-[7px] pl-2 pr-5 no-underline transition-all hover:border-[var(--store-primary,#18181B)] hover:shadow-[var(--store-shadow-soft)]"
24
+ >
25
+ <Image src={collImageSrc(c.name)} alt="" width={44} height={44} className="h-11 w-11 flex-none object-contain" />
26
+ <span className="font-display whitespace-nowrap text-[14px] font-semibold not-italic text-[var(--store-ink)]">{c.name}</span>
27
+ </Link>
28
+ ))}
29
+ </div>
30
+ </div>
31
+ );
32
+ }
@@ -0,0 +1,141 @@
1
+ "use client";
2
+
3
+ // Carrossel de produtos em destaque (combos/ofertas) com add-to-cart funcional.
4
+ // Âncora #destaques — alvo do CTA do hero.
5
+ //
6
+ // ── EDITOR: esta é a VITRINE DA HOME ────────────────────────────────────────
7
+ // `Editable.Vitrine` deixa o LOJISTA escolher o que aparece aqui (uma categoria da Unbox, uma lista
8
+ // de produtos ou uma busca). O documento guarda só a ESCOLHA; quem a transforma em produto com preço
9
+ // e estoque é a LOJA, no servidor (lib/vitrine.ts), e os produtos chegam prontos em `data.vitrines`.
10
+ //
11
+ // O CAMINHO NÃO É CONSTANTE. `path={CAMPO_VITRINE}` é RELATIVO à seção: o primitivo o resolve para
12
+ // `<container>.<id da seção>.vitrine` pelo escopo, e aqui `joinPath(ctx.scope, CAMPO_VITRINE)` deriva
13
+ // EXATAMENTE a mesma string para achar os produtos já resolvidos. Escrever o caminho completo à mão
14
+ // descola os dois lados no primeiro id renomeado, em silêncio.
15
+ //
16
+ // FALLBACK = o que o código mostra hoje (`data.combos`, a regra da página: kits, senão ofertas, senão
17
+ // destaques). Enquanto o lojista não escolher, e também quando a escolha não resolve em nada (Unbox
18
+ // fora, categoria esvaziada), esta seção renderiza o mesmo HTML de antes. Vitrine vazia lê como loja
19
+ // quebrada.
20
+ //
21
+ // O produto resolvido pela escolha traz id, título, foto, preço e estoque, mas não a VARIANTE. O card
22
+ // do catálogo já carregado (com variante, preço antigo e selo) é reaproveitado quando o produto está
23
+ // nele; fora dele, o card nasce mínimo: liga à página do produto e não tem o "+" do carrinho (regra do
24
+ // ProductGridCard: sem variantId, sem botão). Preço e nome continuam sendo dado do catálogo (§8).
25
+ //
26
+ // Na PRÉVIA os produtos vêm pelo 4º argumento do render-prop: o rascunho não passa pelo servidor da
27
+ // loja, então o primitivo pergunta à própria loja (`POST /api/unbox/vitrine`) e entrega em `previa`.
28
+ // `previa.produtos` GANHA de `data.vitrines`. Em produção `previa` é `undefined` e nada aqui muda.
29
+ import * as React from "react";
30
+ import Link from "next/link";
31
+ import { CaretLeft, CaretRight, ArrowRight } from "@phosphor-icons/react/dist/ssr";
32
+ import { useCart } from "@/components/cart/cart-provider";
33
+ import type { CatalogProductItem } from "@/components/catalog/catalog-client";
34
+ import { ProductGridCard } from "@/components/catalog/product-grid-card";
35
+ import { CAMPO_VITRINE, Editable, joinPath, useEditableContext, type VitrineProdutoResolvido, type VitrineValue } from "@/lib/editable";
36
+ import { formatBRL } from "@/lib/format";
37
+ import type { SectionComponentProps } from "./registry";
38
+
39
+ /** Quantos produtos a vitrine mostra quando a escolha do lojista não diz (o mesmo teto da página). */
40
+ const TETO_PADRAO = 10;
41
+
42
+ /** Produto resolvido pela escolha do lojista → item de card (ver o cabeçalho). */
43
+ function itemDaVitrine(p: VitrineProdutoResolvido, catalogo: CatalogProductItem[]): CatalogProductItem {
44
+ const doCatalogo = catalogo.find((c) => c.productId === p.id || c.slug === p.slug);
45
+ if (doCatalogo) return doCatalogo;
46
+ return {
47
+ slug: p.slug,
48
+ title: p.titulo,
49
+ weight: "",
50
+ categories: [],
51
+ price: p.preco ?? 0,
52
+ // `preco: null` = produto sem variante com preço no painel; `formatBRL` devolve "—" (ausência honesta)
53
+ displayPrice: formatBRL(p.preco),
54
+ oldPrice: null,
55
+ displayOld: null,
56
+ offPct: null,
57
+ imageUrl: p.imagem,
58
+ productId: p.id,
59
+ variantId: null,
60
+ badge: null,
61
+ soldOut: p.esgotado,
62
+ };
63
+ }
64
+
65
+ export function CombosCarouselSection({ data }: SectionComponentProps) {
66
+ const { add } = useCart();
67
+ const [busyId, setBusyId] = React.useState<string | null>(null);
68
+ const rowRef = React.useRef<HTMLDivElement | null>(null);
69
+
70
+ // O MESMO caminho que o primitivo registra logo abaixo, derivado do escopo da seção.
71
+ const ctx = useEditableContext();
72
+ const doServidor = data.vitrines?.[joinPath(ctx.scope, CAMPO_VITRINE)] ?? null;
73
+ // A escolha que o CÓDIGO representa: os produtos desta seção hoje, na ordem em que aparecem.
74
+ // Vínculo pelo productId, não pelo slug: ver product-showcase.tsx. Slug muda no painel e o
75
+ // produto some da seção em silêncio; o id é estável.
76
+ const fallbackVitrine: VitrineValue = { modo: "produtos", produtos: data.combos.map((p) => p.productId || p.slug) };
77
+
78
+ // Sem catálogo nenhum a seção não tem o que mostrar. No modo edição ela CONTINUA na tela: sumindo
79
+ // dali, o lojista não conseguiria selecionar a vitrine para trocar a escolha.
80
+ if (data.combos.length === 0 && !doServidor?.length && !ctx.editing) return null;
81
+
82
+ async function quickAdd(p: CatalogProductItem) {
83
+ if (!p.productId || !p.variantId || p.soldOut) return;
84
+ setBusyId(p.variantId);
85
+ await add({ productId: p.productId, variantId: p.variantId, price: p.price, quantity: 1, thumbnail: p.imageUrl ?? undefined, title: p.title });
86
+ setBusyId(null);
87
+ }
88
+ const scrollRow = (dir: number) => rowRef.current?.scrollBy({ left: dir * 300, behavior: "smooth" });
89
+
90
+ return (
91
+ <div id="destaques" className="mx-auto max-w-[var(--container-max,1240px)] scroll-mt-24 px-4 pt-[42px] sm:px-6">
92
+ <div className="mb-[18px] flex items-end justify-between gap-3">
93
+ <div>
94
+ {/* o título de partida é o que a página derivou do catálogo ("Destaques", "Ofertas em destaque") */}
95
+ <Editable.Text as="h2" path="titulo" fallback={data.combosTitle} label="Título" className="font-display text-[26px] font-extrabold" />
96
+ <Editable.Text as="p" path="subtitulo" fallback="Produtos em destaque para você." label="Subtítulo" className="mt-1 text-sm text-[var(--store-muted)]" />
97
+ </div>
98
+ <div className="hidden gap-2.5 sm:flex">
99
+ <button type="button" onClick={() => scrollRow(-1)} aria-label="Anterior" className="flex h-[42px] w-[42px] items-center justify-center rounded-full border-[1.5px] border-[var(--store-line-2)] bg-white hover:border-[var(--store-primary,#18181B)]"><CaretLeft weight="bold" className="text-[var(--store-ink-2)]" /></button>
100
+ <button type="button" onClick={() => scrollRow(1)} aria-label="Próximo" className="flex h-[42px] w-[42px] items-center justify-center rounded-full border-[1.5px] border-[var(--store-line-2)] bg-white hover:border-[var(--store-primary,#18181B)]"><CaretRight weight="bold" className="text-[var(--store-ink-2)]" /></button>
101
+ </div>
102
+ </div>
103
+ {/* O primitivo só REGISTRA o caminho e devolve os atributos de seleção; em produção ele não
104
+ põe nada no DOM: sem escolha do lojista, o HTML aqui é o mesmo de antes. */}
105
+ <Editable.Vitrine path={CAMPO_VITRINE} label="Produtos desta vitrine" fallback={fallbackVitrine}>
106
+ {(escolha, attrs, ref, previa) => {
107
+ // No modo PRODUTOS a lista É a resposta: mostra tantos quantos o lojista escolheu. Nos
108
+ // outros modos o limite decide o tamanho, com o teto da página como padrão.
109
+ const teto = escolha.modo === "produtos" ? escolha.produtos.length : (escolha.limite ?? TETO_PADRAO);
110
+ // a prévia (rascunho de agora) ganha do que o servidor resolveu (documento publicado)
111
+ const resolvidos = previa?.produtos ?? doServidor;
112
+ // LISTA VAZIA cai nos produtos do CÓDIGO, não numa fileira em branco: é o que a página
113
+ // publicada faz (`resolverVitrinesDoDocumento` deixa de fora o caminho que não resolveu em
114
+ // nada). Quem avisa o lojista é a tarja âmbar do primitivo, que só existe na prévia.
115
+ const itens = resolvidos && resolvidos.length ? resolvidos.slice(0, teto).map((p) => itemDaVitrine(p, data.combos)) : data.combos;
116
+ return (
117
+ // dois refs num elemento só: o do carrossel (setas) e o do editor (seleção)
118
+ <div ref={(el) => { rowRef.current = el; ref(el); }} {...attrs} className="flex gap-[18px] overflow-x-auto pb-3.5 [scrollbar-width:thin]">
119
+ {itens.map((c) => (
120
+ <div key={c.slug} className="w-[260px] flex-none">
121
+ <ProductGridCard p={c} busy={busyId === c.variantId} onAdd={() => quickAdd(c)} />
122
+ </div>
123
+ ))}
124
+ {/* card final: ver todos os produtos (o destino é a mecânica do catálogo; as frases são copy) */}
125
+ <Link
126
+ href="/produtos"
127
+ className="group flex w-[260px] flex-none flex-col items-center justify-center gap-3.5 rounded-2xl border-[1.5px] border-dashed border-[var(--store-primary-soft)] bg-white/60 p-6 text-center no-underline transition-colors hover:border-[var(--store-primary,#18181B)] hover:bg-[var(--store-primary-soft,#F1F1F3)]"
128
+ >
129
+ <span className="flex h-14 w-14 items-center justify-center rounded-full bg-[var(--store-primary-soft,#F1F1F3)] text-[var(--store-primary,#18181B)] transition-colors group-hover:bg-[var(--store-primary,#18181B)] group-hover:text-white">
130
+ <ArrowRight weight="bold" className="text-2xl" />
131
+ </span>
132
+ <Editable.Text path="ver-todos.titulo" fallback="Ver todos os produtos" label="Título do card de ver todos" className="font-display text-[17px] font-extrabold leading-[1.2] text-[var(--store-primary,#18181B)]" />
133
+ <Editable.Text path="ver-todos.texto" fallback="Explore o catálogo completo" label="Texto do card de ver todos" className="text-[13px] text-[var(--store-muted)]" />
134
+ </Link>
135
+ </div>
136
+ );
137
+ }}
138
+ </Editable.Vitrine>
139
+ </div>
140
+ );
141
+ }
@@ -0,0 +1,73 @@
1
+ "use client";
2
+
3
+ // Comparativo "nós vs outros" — tabela ESTRUTURAL (a loja de referência usava imagem raster; aqui é
4
+ // componente de verdade: acessível, responsivo e recolorível). Conteúdo via receita.
5
+ // ⚠️ Compare ATRIBUTOS do seu produto, sem citar marcas concorrentes nem inventar dado.
6
+ //
7
+ // EDITOR: título e os dois cabeçalhos de coluna são caminhos da seção; as linhas são uma LISTA
8
+ // editável (`linhas.linha-N`). Em cada linha o atributo é texto; cada célula é OU um ícone
9
+ // (`nos-icone` / `outros-icone`, trocável por imagem) OU um texto (`nos` / `outros`), conforme a
10
+ // receita traz booleano ou frase. São caminhos diferentes de propósito: trocar o TIPO de um caminho
11
+ // descarta a edição do lojista. O divisor entre linhas é regra do container (`[&>*+*>div]`),
12
+ // porque o invólucro do item é display:contents e é ele o irmão no DOM.
13
+ import { CheckCircle, XCircle } from "@phosphor-icons/react/dist/ssr";
14
+ import { Editable, EditableScope } from "@/lib/editable";
15
+ import type { SectionComponentProps } from "./registry";
16
+
17
+ type CompRow = { label: string; us: boolean | string; others: boolean | string };
18
+
19
+ function Cell({ v, coluna }: { v: boolean | string; coluna: "nos" | "outros" }) {
20
+ const onde = coluna === "nos" ? "coluna da loja" : "coluna dos outros";
21
+ if (v === true) {
22
+ return (
23
+ <Editable.Icon path={`${coluna}-icone`} label={`Ícone de sim (${onde})`} size={22} className="mx-auto text-[22px] text-[var(--store-primary,#18181B)]">
24
+ <CheckCircle weight="fill" />
25
+ </Editable.Icon>
26
+ );
27
+ }
28
+ if (v === false) {
29
+ return (
30
+ <Editable.Icon path={`${coluna}-icone`} label={`Ícone de não (${onde})`} size={22} className="mx-auto text-[22px] text-[var(--store-faint)]">
31
+ <XCircle weight="fill" />
32
+ </Editable.Icon>
33
+ );
34
+ }
35
+ return <Editable.Text path={coluna} fallback={v} label={`Texto da célula (${onde})`} className="text-[13px] text-[var(--store-muted)]" />;
36
+ }
37
+
38
+ export function ComparisonSection({ sectionProps = {} }: SectionComponentProps) {
39
+ const {
40
+ title = "Compare e escolha bem",
41
+ usLabel = "Aqui",
42
+ othersLabel = "Por aí",
43
+ } = sectionProps as Record<string, string>;
44
+ const rows = ((sectionProps.rows as CompRow[] | undefined) ?? []).filter((r) => r?.label);
45
+ if (rows.length === 0) return null; // comparativo é afirmação sobre o produto: só com dado da marca
46
+ return (
47
+ <div className="mx-auto max-w-[880px] px-4 pt-[var(--section-gap,52px)] sm:px-6">
48
+ <Editable.Text as="h2" path="titulo" fallback={title} label="Título" className="font-display text-center text-[28px] font-extrabold leading-[1.15]" />
49
+ <div className="mt-7 overflow-hidden rounded-2xl border border-[var(--store-line-2)] bg-white">
50
+ <div className="grid grid-cols-[1.6fr_1fr_1fr] border-b border-[var(--store-line)] bg-[var(--store-surface)] text-center">
51
+ <div />
52
+ <Editable.Text as="div" path="nos" fallback={usLabel} label="Cabeçalho da coluna da loja" className="font-display py-3 text-[14px] font-extrabold text-[var(--store-primary,#18181B)]" />
53
+ <Editable.Text as="div" path="outros" fallback={othersLabel} label="Cabeçalho da coluna dos outros" className="font-display py-3 text-[14px] font-bold text-[var(--store-muted)]" />
54
+ </div>
55
+ <div className="[&>*+*>div]:border-t [&>*+*>div]:border-[var(--store-line)]">
56
+ <EditableScope path="linhas">
57
+ <Editable.Sections nested>
58
+ {rows.map((r, i) => (
59
+ <Editable.Section key={i} item id={`linha-${i + 1}`} label={`Linha ${i + 1}`}>
60
+ <div className="grid grid-cols-[1.6fr_1fr_1fr] items-center text-center">
61
+ <Editable.Text as="div" path="rotulo" fallback={r.label} label="Atributo comparado" className="px-4 py-3.5 text-left text-[14px] font-semibold text-[var(--store-ink-2)]" />
62
+ <div className="py-3.5"><Cell v={r.us} coluna="nos" /></div>
63
+ <div className="py-3.5"><Cell v={r.others} coluna="outros" /></div>
64
+ </div>
65
+ </Editable.Section>
66
+ ))}
67
+ </Editable.Sections>
68
+ </EditableScope>
69
+ </div>
70
+ </div>
71
+ </div>
72
+ );
73
+ }
@@ -0,0 +1,40 @@
1
+ "use client";
2
+
3
+ // História da marca: card com foto de fundo e bloco de texto sobreposto + assinatura.
4
+ // Portado de loja real em produção e genericizado. Âncora #nossa-historia (pode ser alvo do menu).
5
+ //
6
+ // EDITOR: foto, título, texto e assinatura são caminhos da seção. A assinatura só registra caminho
7
+ // quando a receita a traz: não abrimos ponto de "assinatura" numa história que não é assinada.
8
+ // A foto continua passando pelo `Foto` (otimizador do Next): o `Editable.Slot` só lhe entrega o
9
+ // src/alt do lojista e os atributos de seleção.
10
+ import { Editable } from "@/lib/editable";
11
+ import type { SectionComponentProps } from "./registry";
12
+ import { Foto } from "@/components/ui/foto";
13
+
14
+ export function FounderStorySection({ sectionProps = {} }: SectionComponentProps) {
15
+ const {
16
+ title = "Nossa história",
17
+ text = "",
18
+ signature = "",
19
+ image = "/brand/ph/photo-c.svg",
20
+ } = sectionProps as Record<string, string>;
21
+ // História é da marca: sem texto na receita, a seção não renderiza (nada de história genérica).
22
+ if (!text) return null;
23
+ return (
24
+ <div id="nossa-historia" className="reveal mx-auto max-w-[var(--container-max,1240px)] scroll-mt-24 px-4 pt-[var(--section-gap,52px)] sm:px-6">
25
+ <div className="relative overflow-hidden rounded-2xl">
26
+ {/* TODO: foto real de bastidor/fundador (desktop ~21:9) */}
27
+ <Editable.Slot path="imagem" type="image" fallback={{ src: image, alt: "" }} label="Foto de fundo">
28
+ {(v, attrs, ref) => <Foto ref={ref} attrs={attrs} src={v.src} alt={v.alt ?? ""} width={2480} height={840} sizes="(min-width: 1280px) 1240px, 100vw" className="h-[420px] w-full object-cover sm:h-[460px]" />}
29
+ </Editable.Slot>
30
+ <div className="absolute inset-0 flex items-end p-5 sm:items-center sm:p-10">
31
+ <div className="max-w-[480px] rounded-2xl bg-white/95 p-6 sm:p-8">
32
+ <Editable.Text as="h2" path="titulo" fallback={title} label="Título" className="font-display text-[24px] font-extrabold" />
33
+ <Editable.Text as="p" path="texto" fallback={text} label="Texto da história" multiline className="mt-3 text-[14.5px] leading-[1.6] text-[var(--store-ink-2)]" />
34
+ {signature && <Editable.Text as="div" path="assinatura" fallback={signature} label="Assinatura" className="font-display mt-4 text-[18px] font-bold italic text-[var(--store-primary,#18181B)]" />}
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ );
40
+ }