@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.
- package/LICENSE.md +46 -0
- package/README.md +175 -0
- package/bin/cli.js +439 -0
- package/package.json +43 -0
- package/src/colors.js +100 -0
- package/src/presets.js +241 -0
- package/src/theme.js +253 -0
- package/template/.claude/agents/assets-marca.md +40 -0
- package/template/.claude/agents/avaliador-visual.md +48 -0
- package/template/.claude/agents/branding-briefing.md +401 -0
- package/template/.claude/agents/conteudo-secao.md +46 -0
- package/template/.claude/agents/paginas-legais.md +44 -0
- package/template/.claude/settings.json +3 -0
- package/template/.claude/skills/frontend-design/ATTRIBUTION.md +3 -0
- package/template/.claude/skills/frontend-design/LICENSE.txt +177 -0
- package/template/.claude/skills/frontend-design/SKILL.md +55 -0
- package/template/.claude/skills/web-design-guidelines/ATTRIBUTION.md +3 -0
- package/template/.claude/skills/web-design-guidelines/SKILL.md +50 -0
- package/template/.claude/skills/web-design-guidelines/guidelines-snapshot.md +180 -0
- package/template/.env.example +158 -0
- package/template/.mcp.json.example +14 -0
- package/template/.nvmrc +1 -0
- package/template/CLAUDE.md +333 -0
- package/template/COVERAGE.md +80 -0
- package/template/DEPLOY.md +179 -0
- package/template/QA.md +108 -0
- package/template/README.md +152 -0
- package/template/agents/CONSTRUCAO.md +207 -0
- package/template/agents/MANAGER.md +192 -0
- package/template/agents/PADROES.md +255 -0
- package/template/agents/definitions/00-scaffold.md +56 -0
- package/template/agents/definitions/01-layout.md +47 -0
- package/template/agents/definitions/02-homepage.md +70 -0
- package/template/agents/definitions/03-catalog.md +86 -0
- package/template/agents/definitions/04-pdp.md +97 -0
- package/template/agents/definitions/05-cart.md +116 -0
- package/template/agents/definitions/06-checkout.md +146 -0
- package/template/agents/definitions/07-auth.md +130 -0
- package/template/agents/definitions/08-customer.md +55 -0
- package/template/agents/definitions/09-promotions.md +62 -0
- package/template/agents/definitions/10-feedback.md +128 -0
- package/template/agents/definitions/11-seo-infra.md +159 -0
- package/template/agents/definitions/12-deploy.md +173 -0
- package/template/agents/definitions/13-cro.md +275 -0
- package/template/agents/definitions/14-seo.md +388 -0
- package/template/agents/definitions/15-branding.md +26 -0
- package/template/agents/definitions/16-qa-visual.md +138 -0
- package/template/agents/definitions/17-aeo.md +257 -0
- package/template/app/(loja)/busca/page.tsx +72 -0
- package/template/app/(loja)/carrinho/oferta/page.tsx +57 -0
- package/template/app/(loja)/carrinho/page.tsx +160 -0
- package/template/app/(loja)/categoria/[tagSlug]/layout.tsx +31 -0
- package/template/app/(loja)/categoria/[tagSlug]/loading.tsx +17 -0
- package/template/app/(loja)/categoria/[tagSlug]/page.tsx +49 -0
- package/template/app/(loja)/checkout/page.tsx +80 -0
- package/template/app/(loja)/checkout/pix/[ref]/page.tsx +133 -0
- package/template/app/(loja)/conta/assinaturas/[referenceId]/page.tsx +112 -0
- package/template/app/(loja)/conta/assinaturas/page.tsx +51 -0
- package/template/app/(loja)/conta/enderecos/page.tsx +20 -0
- package/template/app/(loja)/conta/entrar/page.tsx +119 -0
- package/template/app/(loja)/conta/page.tsx +45 -0
- package/template/app/(loja)/conta/pedidos/[referenceId]/page.tsx +49 -0
- package/template/app/(loja)/conta/pedidos/page.tsx +104 -0
- package/template/app/(loja)/conta/preferencias/page.tsx +24 -0
- package/template/app/(loja)/devolucoes/page.tsx +36 -0
- package/template/app/(loja)/layout.tsx +22 -0
- package/template/app/(loja)/oferta/page.tsx +57 -0
- package/template/app/(loja)/page.tsx +86 -0
- package/template/app/(loja)/pedido/[referenceId]/page.tsx +78 -0
- package/template/app/(loja)/privacidade/page.tsx +113 -0
- package/template/app/(loja)/produto/[productSlug]/layout.tsx +23 -0
- package/template/app/(loja)/produto/[productSlug]/loading.tsx +20 -0
- package/template/app/(loja)/produto/[productSlug]/page.tsx +372 -0
- package/template/app/(loja)/produtos/loading.tsx +16 -0
- package/template/app/(loja)/produtos/page.tsx +33 -0
- package/template/app/(loja)/termos/page.tsx +104 -0
- package/template/app/acesso/page.tsx +129 -0
- package/template/app/api/account/addresses/route.ts +39 -0
- package/template/app/api/account/exists/route.ts +21 -0
- package/template/app/api/account/me/route.ts +25 -0
- package/template/app/api/account/otp/route.ts +26 -0
- package/template/app/api/account/preferences/route.ts +19 -0
- package/template/app/api/account/signin/route.ts +27 -0
- package/template/app/api/account/signout/route.ts +9 -0
- package/template/app/api/acesso/route.ts +171 -0
- package/template/app/api/capi/route.ts +34 -0
- package/template/app/api/cart/coupon/route.ts +49 -0
- package/template/app/api/cart/items/route.ts +45 -0
- package/template/app/api/cart/link/route.ts +69 -0
- package/template/app/api/cart/route.ts +113 -0
- package/template/app/api/cart/share/route.ts +21 -0
- package/template/app/api/cep/[code]/route.ts +18 -0
- package/template/app/api/checkout/address/route.ts +24 -0
- package/template/app/api/checkout/email/route.ts +41 -0
- package/template/app/api/checkout/installments/route.ts +16 -0
- package/template/app/api/checkout/route.ts +110 -0
- package/template/app/api/checkout/shipping/route.ts +65 -0
- package/template/app/api/checkout-destination/route.ts +31 -0
- package/template/app/api/order/[ref]/route.ts +17 -0
- package/template/app/api/payment-link/route.ts +28 -0
- package/template/app/api/revalidate/route.ts +52 -0
- package/template/app/api/shipping/quote/route.ts +29 -0
- package/template/app/api/subscriptions/[id]/route.ts +52 -0
- package/template/app/api/track/route.ts +28 -0
- package/template/app/api/unbox/catalogo/route.ts +136 -0
- package/template/app/api/unbox/paginas/route.ts +167 -0
- package/template/app/api/unbox/vitrine/route.ts +84 -0
- package/template/app/api/webhooks/unbox/route.ts +97 -0
- package/template/app/apple-icon.svg +5 -0
- package/template/app/error.tsx +15 -0
- package/template/app/globals.css +457 -0
- package/template/app/icon.svg +4 -0
- package/template/app/layout.tsx +90 -0
- package/template/app/llms.txt/route.ts +43 -0
- package/template/app/manifest.ts +21 -0
- package/template/app/not-found.tsx +18 -0
- package/template/app/opengraph-image.tsx +35 -0
- package/template/app/robots.ts +31 -0
- package/template/app/sitemap.ts +42 -0
- package/template/bootstrap.sh +87 -0
- package/template/components/account/account-shell.tsx +47 -0
- package/template/components/account/address-book.tsx +147 -0
- package/template/components/account/preferences-form.tsx +77 -0
- package/template/components/account/reorder-button.tsx +47 -0
- package/template/components/account/signout-button.tsx +20 -0
- package/template/components/account/subscription-actions.tsx +204 -0
- package/template/components/account-nav.tsx +41 -0
- package/template/components/address-fields.tsx +102 -0
- package/template/components/analytics/data-layer-ready.tsx +16 -0
- package/template/components/analytics/purchase-tracker.tsx +32 -0
- package/template/components/brand-search.tsx +34 -0
- package/template/components/cart/cart-provider.tsx +340 -0
- package/template/components/cart/mini-cart.tsx +228 -0
- package/template/components/cart-button.tsx +35 -0
- package/template/components/catalog/catalog-client.tsx +523 -0
- package/template/components/catalog/grid-skeleton.tsx +19 -0
- package/template/components/catalog/pager.tsx +66 -0
- package/template/components/catalog/product-grid-card.tsx +52 -0
- package/template/components/catalog/product-grid.tsx +27 -0
- package/template/components/checkout/checkout-client.tsx +1333 -0
- package/template/components/chrome/announce-bar.tsx +57 -0
- package/template/components/chrome/chrome-recipe.ts +13 -0
- package/template/components/chrome/footers/captura-botao.tsx +28 -0
- package/template/components/chrome/footers/colunas.tsx +110 -0
- package/template/components/chrome/footers/conversao.tsx +90 -0
- package/template/components/chrome/footers/editorial.tsx +98 -0
- package/template/components/chrome/footers/minimal.tsx +55 -0
- package/template/components/chrome/header-bar-mobile.tsx +64 -0
- package/template/components/chrome/headers/centralizado.tsx +71 -0
- package/template/components/chrome/headers/classico.tsx +64 -0
- package/template/components/chrome/headers/compacto.tsx +66 -0
- package/template/components/chrome/headers/equilibrado.tsx +73 -0
- package/template/components/chrome/headers/imersivo.tsx +62 -0
- package/template/components/chrome/registry.ts +70 -0
- package/template/components/chrome/solid-on-scroll.tsx +39 -0
- package/template/components/empty-state.tsx +22 -0
- package/template/components/home/combo-card-compact.tsx +67 -0
- package/template/components/home/combos-home.tsx +64 -0
- package/template/components/home/combos-section.tsx +177 -0
- package/template/components/home/home-recipe.ts +41 -0
- package/template/components/home/sections/attributes-marquee.tsx +178 -0
- package/template/components/home/sections/benefits.tsx +80 -0
- package/template/components/home/sections/bloco-html.tsx +47 -0
- package/template/components/home/sections/catalogo.ts +165 -0
- package/template/components/home/sections/category-pills.tsx +32 -0
- package/template/components/home/sections/combos-carousel.tsx +141 -0
- package/template/components/home/sections/comparison.tsx +73 -0
- package/template/components/home/sections/founder-story.tsx +40 -0
- package/template/components/home/sections/hero.tsx +187 -0
- package/template/components/home/sections/kits.tsx +11 -0
- package/template/components/home/sections/media-cards.tsx +70 -0
- package/template/components/home/sections/newsletter.tsx +62 -0
- package/template/components/home/sections/product-showcase.tsx +175 -0
- package/template/components/home/sections/purchase-hero.tsx +187 -0
- package/template/components/home/sections/quote-banner.tsx +50 -0
- package/template/components/home/sections/registry.ts +186 -0
- package/template/components/home/sections/reviews-carousel.tsx +66 -0
- package/template/components/home/sections/reviews.tsx +68 -0
- package/template/components/home/sections/ritual.tsx +96 -0
- package/template/components/home/sections/savings.tsx +47 -0
- package/template/components/home/sections/social-row.tsx +65 -0
- package/template/components/home/sections/spec-table.tsx +117 -0
- package/template/components/home/sections/stars.tsx +12 -0
- package/template/components/home/sections/stats-grid.tsx +54 -0
- package/template/components/home/sections/trust-bar.tsx +56 -0
- package/template/components/home/sections/trust-strip.tsx +40 -0
- package/template/components/home/sections/video-wall.tsx +59 -0
- package/template/components/landing/landing-recipe.ts +20 -0
- package/template/components/landing/oferta-sections.tsx +40 -0
- package/template/components/landing/product-picker.tsx +257 -0
- package/template/components/mobile-nav.tsx +71 -0
- package/template/components/order-status.tsx +163 -0
- package/template/components/powered-by-unbox.tsx +27 -0
- package/template/components/product/pdp/buy-box.tsx +604 -0
- package/template/components/product/pdp/catalog-grid.tsx +113 -0
- package/template/components/product/pdp/faq-modelo.ts +86 -0
- package/template/components/product/pdp/gallery.tsx +87 -0
- package/template/components/product/pdp/interactive.tsx +200 -0
- package/template/components/product/pdp/newsletter.tsx +64 -0
- package/template/components/product/pdp/payment-chips.tsx +22 -0
- package/template/components/product/pdp/pdp-view.tsx +227 -0
- package/template/components/product/pdp/recommendations.tsx +144 -0
- package/template/components/product/pdp/sections.tsx +173 -0
- package/template/components/quantity-stepper.tsx +36 -0
- package/template/components/search-box.tsx +37 -0
- package/template/components/site-footer.tsx +59 -0
- package/template/components/site-header.tsx +64 -0
- package/template/components/ui/accordion.tsx +72 -0
- package/template/components/ui/alert-dialog.tsx +187 -0
- package/template/components/ui/alert.tsx +76 -0
- package/template/components/ui/aspect-ratio.tsx +22 -0
- package/template/components/ui/avatar.tsx +109 -0
- package/template/components/ui/badge.tsx +52 -0
- package/template/components/ui/breadcrumb.tsx +125 -0
- package/template/components/ui/button.tsx +58 -0
- package/template/components/ui/card.tsx +103 -0
- package/template/components/ui/carousel.tsx +242 -0
- package/template/components/ui/command.tsx +196 -0
- package/template/components/ui/dialog.tsx +160 -0
- package/template/components/ui/dropdown-menu.tsx +268 -0
- package/template/components/ui/foto.tsx +54 -0
- package/template/components/ui/input-group.tsx +158 -0
- package/template/components/ui/input-otp.tsx +87 -0
- package/template/components/ui/input.tsx +20 -0
- package/template/components/ui/label.tsx +20 -0
- package/template/components/ui/navigation-menu.tsx +168 -0
- package/template/components/ui/pagination.tsx +132 -0
- package/template/components/ui/popover.tsx +90 -0
- package/template/components/ui/progress.tsx +83 -0
- package/template/components/ui/radio-group.tsx +38 -0
- package/template/components/ui/scroll-area.tsx +55 -0
- package/template/components/ui/select.tsx +201 -0
- package/template/components/ui/separator.tsx +25 -0
- package/template/components/ui/sheet.tsx +138 -0
- package/template/components/ui/skeleton.tsx +13 -0
- package/template/components/ui/sonner.tsx +44 -0
- package/template/components/ui/switch.tsx +32 -0
- package/template/components/ui/table.tsx +116 -0
- package/template/components/ui/tabs.tsx +82 -0
- package/template/components/ui/textarea.tsx +18 -0
- package/template/components/ui/toggle-group.tsx +89 -0
- package/template/components/ui/toggle.tsx +45 -0
- package/template/components/ui/tooltip.tsx +66 -0
- package/template/components.json +25 -0
- package/template/eslint.config.mjs +36 -0
- package/template/gitignore +21 -0
- package/template/lib/analytics.ts +369 -0
- package/template/lib/api.ts +60 -0
- package/template/lib/capi.ts +87 -0
- package/template/lib/cart-link.ts +51 -0
- package/template/lib/cart-normalize.ts +142 -0
- package/template/lib/cart-recovery.ts +23 -0
- package/template/lib/cart-response.ts +48 -0
- package/template/lib/catalog-map.ts +54 -0
- package/template/lib/catalog.ts +3 -0
- package/template/lib/checkout-lock.ts +21 -0
- package/template/lib/checkout-nav.ts +22 -0
- package/template/lib/config.ts +51 -0
- package/template/lib/crm.ts +40 -0
- package/template/lib/customer-session.ts +21 -0
- package/template/lib/dataloader.ts +23 -0
- package/template/lib/editable/config.ts +11 -0
- package/template/lib/editable/document.ts +1762 -0
- package/template/lib/editable/index.ts +3 -0
- package/template/lib/editable/primitives.tsx +821 -0
- package/template/lib/editable/provider.tsx +861 -0
- package/template/lib/editable/rastreio-navegacao.tsx +65 -0
- package/template/lib/editable/rastreio.tsx +168 -0
- package/template/lib/editable/server.ts +189 -0
- package/template/lib/editable/tokens.ts +22 -0
- package/template/lib/editable/verify.ts +24 -0
- package/template/lib/enrichment/combos.ts +184 -0
- package/template/lib/enrichment/index.ts +188 -0
- package/template/lib/enrichment/products.json +1 -0
- package/template/lib/env-check.ts +47 -0
- package/template/lib/format.ts +130 -0
- package/template/lib/icons.ts +67 -0
- package/template/lib/json-ld.ts +9 -0
- package/template/lib/llms-txt.ts +75 -0
- package/template/lib/mockup.ts +24 -0
- package/template/lib/newsletter.ts +12 -0
- package/template/lib/orders.ts +160 -0
- package/template/lib/queries.ts +66 -0
- package/template/lib/ratelimit.ts +47 -0
- package/template/lib/rotas-editaveis.ts +192 -0
- package/template/lib/sanitize.ts +36 -0
- package/template/lib/schemas.ts +78 -0
- package/template/lib/session.ts +98 -0
- package/template/lib/store-config.ts +67 -0
- package/template/lib/unbox/client.ts +829 -0
- package/template/lib/unbox/customer.ts +224 -0
- package/template/lib/unbox/errors.ts +97 -0
- package/template/lib/unbox/index.ts +10 -0
- package/template/lib/unbox/store.ts +113 -0
- package/template/lib/unbox/types.ts +195 -0
- package/template/lib/unbox/webhooks.ts +66 -0
- package/template/lib/utils.ts +6 -0
- package/template/lib/vitrine.ts +242 -0
- package/template/lib/webhook-store.ts +22 -0
- package/template/middleware.ts +178 -0
- package/template/next.config.ts +63 -0
- package/template/package.json +62 -0
- package/template/postcss.config.mjs +5 -0
- package/template/public/brand/coll/default.png +0 -0
- package/template/public/brand/hero-desktop.svg +12 -0
- package/template/public/brand/hero-mobile.svg +12 -0
- package/template/public/brand/heros/boutique-desktop.svg +11 -0
- package/template/public/brand/heros/boutique-mobile.svg +11 -0
- package/template/public/brand/heros/editorial-desktop.svg +11 -0
- package/template/public/brand/heros/editorial-mobile.svg +11 -0
- package/template/public/brand/heros/essencial-desktop.svg +12 -0
- package/template/public/brand/heros/essencial-mobile.svg +12 -0
- package/template/public/brand/heros/promocional-desktop.svg +12 -0
- package/template/public/brand/heros/promocional-mobile.svg +12 -0
- package/template/public/brand/logo-chrome.svg +6 -0
- package/template/public/brand/logo-white.svg +6 -0
- package/template/public/brand/logo.svg +7 -0
- package/template/public/brand/pay-amex.webp +0 -0
- package/template/public/brand/pay-elo.webp +0 -0
- package/template/public/brand/pay-mastercard.webp +0 -0
- package/template/public/brand/pay-pix.webp +0 -0
- package/template/public/brand/pay-visa.webp +0 -0
- package/template/public/brand/ph/avatar-a.svg +5 -0
- package/template/public/brand/ph/avatar-b.svg +5 -0
- package/template/public/brand/ph/avatar-c.svg +5 -0
- package/template/public/brand/ph/avatar-d.svg +5 -0
- package/template/public/brand/ph/photo-a.svg +7 -0
- package/template/public/brand/ph/photo-b.svg +6 -0
- package/template/public/brand/ph/photo-c.svg +6 -0
- package/template/public/brand/ph/poster-a.svg +6 -0
- package/template/public/brand/ph/poster-b.svg +6 -0
- package/template/public/brand/ph/poster-c.svg +6 -0
- package/template/public/unbox/powered-by-fundo-preto.png +0 -0
- package/template/public/unbox/powered-by-transparente.png +0 -0
- package/template/public/unbox/powered-by.png +0 -0
- package/template/scripts/abandoned-cart.ts +82 -0
- package/template/scripts/check-editable.mjs +467 -0
- package/template/scripts/check-honestidade.mjs +111 -0
- package/template/scripts/check-placeholder.mjs +200 -0
- package/template/scripts/check-recipe.mjs +76 -0
- package/template/scripts/check-unbox-brand.mjs +298 -0
- package/template/scripts/contraste.js +177 -0
- package/template/scripts/dump-catalog.ts +95 -0
- package/template/scripts/load-env.ts +13 -0
- package/template/scripts/medir-sistema.mjs +56 -0
- package/template/scripts/place-order-pix.ts +61 -0
- package/template/scripts/qa-screenshots.mjs +150 -0
- package/template/scripts/sistema.py +231 -0
- package/template/scripts/subscribe-webhook.ts +36 -0
- package/template/scripts/test-live.ts +186 -0
- package/template/scripts/vocabulario.py +264 -0
- package/template/tsconfig.json +27 -0
- package/tools/LEIA-ME.md +39 -0
- package/tools/check-template-neutro.mjs +111 -0
- package/tools/notion-doc.mjs +191 -0
- package/tools/tokenize-neutrals.mjs +0 -0
- package/tools/tokenize-radius.mjs +52 -0
- package/tools/workflow-storefront.legado.js +1207 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
/* ═══════════════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
MAPA DESTE ARQUIVO — ~420 linhas, e quase toda edição de marca mora numa só delas.
|
|
3
|
+
|
|
4
|
+
Na ordem em que aparecem (busque pelo marcador, não conte linhas — elas andam):
|
|
5
|
+
|
|
6
|
+
@import Tailwind v4, tw-animate-css, shadcn
|
|
7
|
+
@theme inline ponte tokens --store-* → utilitários do Tailwind/shadcn.
|
|
8
|
+
Derivação, não decisão: você raramente mexe aqui.
|
|
9
|
+
:root ◀ AS DECISÕES. Cor, fonte, raio, ritmo vertical, largura de
|
|
10
|
+
container e eixos de composição (--card-*). A marca entra AQUI.
|
|
11
|
+
@layer base reset e defaults de elemento
|
|
12
|
+
"PDP layout" página de produto e cards
|
|
13
|
+
"Marquee"/"Chama" animações pontuais
|
|
14
|
+
"Checkout" chrome próprio, full-bleed, nav inferior mobile
|
|
15
|
+
"Header SOBREPOSTO" variante "imersivo" do chrome
|
|
16
|
+
"HTML rico" descrição do produto vinda da API, sanitizada
|
|
17
|
+
"── MOTION" token --motion e animações de scroll (valor 0 desliga tudo)
|
|
18
|
+
|
|
19
|
+
Pra trocar a paleta da marca, só o :root importa. Não leia o resto do arquivo.
|
|
20
|
+
═══════════════════════════════════════════════════════════════════════════════════════ */
|
|
21
|
+
@import "tailwindcss";
|
|
22
|
+
@import "tw-animate-css";
|
|
23
|
+
@import "shadcn/tailwind.css";
|
|
24
|
+
|
|
25
|
+
@custom-variant dark (&:is(.dark *));
|
|
26
|
+
|
|
27
|
+
@theme inline {
|
|
28
|
+
/* Fontes — as famílias reais são declaradas no bloco UNBOX-FONTS de app/layout.tsx
|
|
29
|
+
(reescrito pelo create-unbox-store conforme o estilo/preset escolhido). */
|
|
30
|
+
--font-sans: var(--font-sans);
|
|
31
|
+
--font-mono: var(--font-geist-mono);
|
|
32
|
+
--font-heading: var(--font-display);
|
|
33
|
+
|
|
34
|
+
/* Cores semânticas → tokens derivados */
|
|
35
|
+
--color-background: var(--background);
|
|
36
|
+
--color-foreground: var(--foreground);
|
|
37
|
+
--color-card: var(--card);
|
|
38
|
+
--color-card-foreground: var(--card-foreground);
|
|
39
|
+
--color-popover: var(--popover);
|
|
40
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
41
|
+
--color-primary: var(--primary);
|
|
42
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
43
|
+
--color-secondary: var(--secondary);
|
|
44
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
45
|
+
--color-muted: var(--muted);
|
|
46
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
47
|
+
--color-accent: var(--accent);
|
|
48
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
49
|
+
--color-destructive: var(--destructive);
|
|
50
|
+
--color-border: var(--border);
|
|
51
|
+
--color-input: var(--input);
|
|
52
|
+
--color-ring: var(--ring);
|
|
53
|
+
|
|
54
|
+
/* Semânticos de e-commerce */
|
|
55
|
+
--color-price: var(--price);
|
|
56
|
+
--color-success: var(--success);
|
|
57
|
+
--color-rating: var(--rating);
|
|
58
|
+
--color-warning: var(--warning);
|
|
59
|
+
|
|
60
|
+
/* Brand (compat) */
|
|
61
|
+
--color-brand: var(--brand);
|
|
62
|
+
--color-brand-soft: var(--brand-soft);
|
|
63
|
+
|
|
64
|
+
/* Charts */
|
|
65
|
+
--color-chart-1: var(--chart-1);
|
|
66
|
+
--color-chart-2: var(--chart-2);
|
|
67
|
+
--color-chart-3: var(--chart-3);
|
|
68
|
+
--color-chart-4: var(--chart-4);
|
|
69
|
+
--color-chart-5: var(--chart-5);
|
|
70
|
+
|
|
71
|
+
/* Sidebar */
|
|
72
|
+
--color-sidebar: var(--sidebar);
|
|
73
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
74
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
75
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
76
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
77
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
78
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
79
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
80
|
+
|
|
81
|
+
/* Radius — escala inteira deriva de --radius (escrito pelo CLI conforme o estilo).
|
|
82
|
+
Multiplicadores calibrados pros raios usados no design (lg=14px, xl≈18px, 2xl≈22px
|
|
83
|
+
com o default 0.875rem). */
|
|
84
|
+
--radius-sm: calc(var(--radius) * 0.6);
|
|
85
|
+
--radius-md: calc(var(--radius) * 0.8);
|
|
86
|
+
--radius-lg: var(--radius);
|
|
87
|
+
--radius-xl: calc(var(--radius) * 1.3);
|
|
88
|
+
--radius-2xl: calc(var(--radius) * 1.6);
|
|
89
|
+
--radius-3xl: calc(var(--radius) * 2.2);
|
|
90
|
+
--radius-4xl: calc(var(--radius) * 2.6);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
:root {
|
|
94
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
95
|
+
BRAND TOKENS — editar aqui ao criar uma nova loja.
|
|
96
|
+
Escritos pelo create-unbox-store (cores da marca + estilo/preset).
|
|
97
|
+
|
|
98
|
+
⚠️ O cinza neutro abaixo é CANVAS PROVISÓRIA, não escolha estética: é a tela mais
|
|
99
|
+
neutra possível pra qualquer cor de marca cair bem em cima. Aplicar as cores REAIS
|
|
100
|
+
da marca é o primeiro ato do briefing — loja entregue com esta paleta é loja sem
|
|
101
|
+
identidade (o `npm run build` avisa enquanto ela estiver aqui).
|
|
102
|
+
═══════════════════════════════════════════════════════════════
|
|
103
|
+
|
|
104
|
+
--store-primary botões, preços, links ativos, ícones de ação
|
|
105
|
+
--store-primary-dark hover e pressed dos botões primários
|
|
106
|
+
--store-primary-soft fundo suave: badges de verificado, avisos verdes
|
|
107
|
+
--store-cta banners de oferta, newsletter, badge de desconto, "comprar agora"
|
|
108
|
+
--store-cta-fg texto sobre a cor CTA
|
|
109
|
+
--store-cta-dark hover/pressed dos elementos com fundo CTA
|
|
110
|
+
--store-cta-soft fundo suave da família CTA (chips, avisos)
|
|
111
|
+
--store-chrome-bg header e footer (barra da marca)
|
|
112
|
+
--store-chrome-text texto sobre o chrome
|
|
113
|
+
--store-chrome-muted texto secundário/ícones sobre o chrome
|
|
114
|
+
--store-chrome-line divisores dentro do chrome (rodapé)
|
|
115
|
+
─────────────────────────────────────────────────────────────── */
|
|
116
|
+
--store-primary: #18181B;
|
|
117
|
+
--store-primary-dark: #09090B;
|
|
118
|
+
--store-primary-soft: #F1F1F3;
|
|
119
|
+
--store-cta: #D97706;
|
|
120
|
+
--store-cta-fg: #1C1207;
|
|
121
|
+
--store-cta-dark: #B45309;
|
|
122
|
+
--store-cta-soft: #FDF0DC;
|
|
123
|
+
--store-chrome-bg: #18181B;
|
|
124
|
+
--store-chrome-text: #FFFFFF;
|
|
125
|
+
--store-chrome-muted: #A1A1AA;
|
|
126
|
+
--store-chrome-line: #34343A;
|
|
127
|
+
|
|
128
|
+
/* ── Neutros do ESTILO (mudam por preset, não pela cor da marca) ──
|
|
129
|
+
--store-ink texto principal (quase preto do estilo)
|
|
130
|
+
--store-ink-2 texto de apoio forte, ícones escuros
|
|
131
|
+
--store-muted texto secundário
|
|
132
|
+
--store-faint texto terciário, placeholders, bordas de foco suaves
|
|
133
|
+
--store-bg fundo global das páginas
|
|
134
|
+
--store-surface superfícies elevadas claras (cards suaves)
|
|
135
|
+
--store-surface-2 superfícies de destaque (faixas, blocos de seção)
|
|
136
|
+
--store-line bordas e divisores padrão
|
|
137
|
+
--store-line-2 bordas mais marcadas (cards, inputs)
|
|
138
|
+
--store-sale preço riscado/urgência/erro comercial (vermelho do estilo)
|
|
139
|
+
--store-sale-soft fundo suave do vermelho comercial */
|
|
140
|
+
--store-ink: #18181B;
|
|
141
|
+
--store-ink-2: #3F3F46;
|
|
142
|
+
--store-muted: #71717A;
|
|
143
|
+
--store-faint: #A1A1AA;
|
|
144
|
+
|
|
145
|
+
/* ── SOMBRAS ─────────────────────────────────────────────────────────────────────────
|
|
146
|
+
NUNCA escreva cor literal em box-shadow. Foi exatamente assim que o verde da
|
|
147
|
+
foundation original sobreviveu a uma varredura que trocou 304 cores literais: ela
|
|
148
|
+
olhou classes de cor, e estes estavam dentro de `shadow-[...]` e de `boxShadow`
|
|
149
|
+
inline. Numa loja cobalto ou âmbar, o brilho continuava verde.
|
|
150
|
+
A sombra de CTA acompanha a marca via color-mix; as de card são neutras de
|
|
151
|
+
propósito (sombra é luz, não identidade). */
|
|
152
|
+
--store-shadow-cta: 0 6px 16px color-mix(in srgb, var(--store-primary, #18181B) 28%, transparent);
|
|
153
|
+
--store-shadow-cta-sm: 0 4px 10px color-mix(in srgb, var(--store-primary, #18181B) 30%, transparent);
|
|
154
|
+
--store-shadow-glow: 0 4px 12px color-mix(in srgb, var(--store-primary, #18181B) 32%, transparent);
|
|
155
|
+
--store-shadow-card: 0 10px 30px rgba(24, 24, 27, .10);
|
|
156
|
+
--store-shadow-card-lg: 0 12px 34px rgba(24, 24, 27, .18);
|
|
157
|
+
--store-shadow-soft: 0 4px 14px rgba(24, 24, 27, .07);
|
|
158
|
+
--store-shadow-hover: 0 8px 24px rgba(24, 24, 27, .08);
|
|
159
|
+
--store-bg: #FAFAFA;
|
|
160
|
+
--store-surface: #FFFFFF;
|
|
161
|
+
--store-surface-2: #F4F4F5;
|
|
162
|
+
--store-line: #E4E4E7;
|
|
163
|
+
--store-line-2: #D4D4D8;
|
|
164
|
+
--store-sale: #DC2626;
|
|
165
|
+
--store-sale-soft: #FEE9E9;
|
|
166
|
+
|
|
167
|
+
/* ── Eixos de COMPOSIÇÃO (linguagem visual, mudam por estilo) ──
|
|
168
|
+
--section-gap ritmo vertical entre seções da home/landing (denso ↔ arejado)
|
|
169
|
+
--container-max largura do conteúdo das seções (1240px padrão; menor = editorial)
|
|
170
|
+
--card-bg/border/shadow chrome dos cards de seção (.store-card):
|
|
171
|
+
bordado (default) · sombra flutuante · preenchido flat */
|
|
172
|
+
--section-gap: 52px;
|
|
173
|
+
--container-max: 1240px;
|
|
174
|
+
--card-bg: #ffffff;
|
|
175
|
+
--card-border: 1px solid var(--store-line);
|
|
176
|
+
--card-shadow: none;
|
|
177
|
+
/* ═══════════════════════════════════════════════════════════════ */
|
|
178
|
+
|
|
179
|
+
/* ── Tokens derivados dos brand tokens acima ────────────────── */
|
|
180
|
+
|
|
181
|
+
/* Shadcn/Tailwind — primary aponta diretamente para o brand token */
|
|
182
|
+
--primary: var(--store-primary);
|
|
183
|
+
--primary-foreground: #ffffff;
|
|
184
|
+
|
|
185
|
+
/* CTA / accent */
|
|
186
|
+
--accent: var(--store-cta);
|
|
187
|
+
--accent-foreground: var(--store-cta-fg);
|
|
188
|
+
|
|
189
|
+
/* Semânticos derivados */
|
|
190
|
+
--price: var(--store-primary);
|
|
191
|
+
--success: var(--store-primary);
|
|
192
|
+
--rating: var(--store-cta);
|
|
193
|
+
--warning: var(--store-cta);
|
|
194
|
+
|
|
195
|
+
/* Compat — código existente que referencia --brand-* ou --brand */
|
|
196
|
+
--brand: var(--store-primary);
|
|
197
|
+
--brand-soft: var(--store-primary-soft);
|
|
198
|
+
|
|
199
|
+
/* ── Superfícies e neutros derivados (a base vem dos tokens de ESTILO acima) ── */
|
|
200
|
+
--background: var(--store-bg);
|
|
201
|
+
--foreground: var(--store-ink);
|
|
202
|
+
|
|
203
|
+
--card: oklch(1 0 0);
|
|
204
|
+
--card-foreground: var(--store-ink);
|
|
205
|
+
--popover: oklch(1 0 0);
|
|
206
|
+
--popover-foreground: var(--store-ink);
|
|
207
|
+
|
|
208
|
+
--secondary: var(--store-surface-2);
|
|
209
|
+
--secondary-foreground: var(--store-ink-2);
|
|
210
|
+
|
|
211
|
+
--muted: var(--store-surface-2);
|
|
212
|
+
--muted-foreground: var(--store-muted);
|
|
213
|
+
|
|
214
|
+
--destructive: oklch(0.561 0.218 26.977);
|
|
215
|
+
|
|
216
|
+
--border: var(--store-line);
|
|
217
|
+
--input: var(--store-line);
|
|
218
|
+
--ring: var(--store-primary);
|
|
219
|
+
|
|
220
|
+
/* Radius */
|
|
221
|
+
--radius: 0.875rem;
|
|
222
|
+
|
|
223
|
+
/* Sidebar / mobile bottom nav */
|
|
224
|
+
--sidebar: oklch(1 0 0);
|
|
225
|
+
--sidebar-foreground: var(--store-ink);
|
|
226
|
+
--sidebar-primary: var(--store-primary);
|
|
227
|
+
--sidebar-primary-foreground: #ffffff;
|
|
228
|
+
--sidebar-accent: var(--store-primary-soft);
|
|
229
|
+
--sidebar-accent-foreground: var(--store-ink-2);
|
|
230
|
+
--sidebar-border: var(--store-line);
|
|
231
|
+
--sidebar-ring: var(--store-primary);
|
|
232
|
+
|
|
233
|
+
/* Charts */
|
|
234
|
+
--chart-1: var(--store-primary);
|
|
235
|
+
--chart-2: var(--store-cta);
|
|
236
|
+
--chart-3: oklch(0.561 0.218 26.977);
|
|
237
|
+
--chart-4: oklch(0.603 0.129 118.456);
|
|
238
|
+
--chart-5: oklch(0.685 0.147 71.678);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.dark {
|
|
242
|
+
/* Brand tokens herdados do :root — não precisam mudar no dark mode.
|
|
243
|
+
Apenas as superfícies e neutros são reajustados. */
|
|
244
|
+
|
|
245
|
+
--background: oklch(0.18 0.02 260); /* neutro escuro genérico */
|
|
246
|
+
--foreground: oklch(0.95 0.005 90);
|
|
247
|
+
|
|
248
|
+
--card: oklch(0.22 0.02 260);
|
|
249
|
+
--card-foreground: oklch(0.95 0.005 90);
|
|
250
|
+
--popover: oklch(0.22 0.02 260);
|
|
251
|
+
--popover-foreground: oklch(0.95 0.005 90);
|
|
252
|
+
|
|
253
|
+
/* primary/accent continuam apontando para --store-* via :root */
|
|
254
|
+
|
|
255
|
+
--secondary: oklch(0.28 0.02 260);
|
|
256
|
+
--secondary-foreground: oklch(0.95 0.005 90);
|
|
257
|
+
|
|
258
|
+
--muted: oklch(0.28 0.02 260);
|
|
259
|
+
--muted-foreground: oklch(0.65 0.01 90);
|
|
260
|
+
|
|
261
|
+
--destructive: oklch(0.62 0.23 27);
|
|
262
|
+
|
|
263
|
+
--border: oklch(1 0 0 / 10%);
|
|
264
|
+
--input: oklch(1 0 0 / 14%);
|
|
265
|
+
|
|
266
|
+
--sidebar: oklch(0.22 0.02 260);
|
|
267
|
+
--sidebar-foreground: oklch(0.95 0.005 90);
|
|
268
|
+
--sidebar-border: oklch(1 0 0 / 10%);
|
|
269
|
+
|
|
270
|
+
--chart-3: oklch(0.62 0.23 27);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
@layer base {
|
|
274
|
+
* {
|
|
275
|
+
@apply border-border outline-ring/50;
|
|
276
|
+
}
|
|
277
|
+
body {
|
|
278
|
+
@apply bg-background text-foreground;
|
|
279
|
+
/* A família vive AQUI, não no <html>: as variáveis do next/font (--font-sans etc.)
|
|
280
|
+
são declaradas na className do <body>, então uma regra em html não as enxerga e
|
|
281
|
+
o corpo cai no serif do navegador. Só aparecia no build de produção, e só fora
|
|
282
|
+
do .store-layout (ex.: /carrinho). */
|
|
283
|
+
font-family: var(--font-sans), system-ui, -apple-system, sans-serif;
|
|
284
|
+
}
|
|
285
|
+
html {
|
|
286
|
+
/* Mata a barra de rolagem HORIZONTAL acidental: o hack .full-bleed usa 100vw, e 100vw
|
|
287
|
+
INCLUI a largura da barra de rolagem vertical (Windows/Linux e Mac com scrollbar
|
|
288
|
+
fixa) — sobram ~15px além da tela. `clip` corta o excedente SEM criar um scroll
|
|
289
|
+
container novo (não quebra position: sticky nem scrollIntoView). */
|
|
290
|
+
overflow-x: clip;
|
|
291
|
+
}
|
|
292
|
+
@supports not (overflow: clip) {
|
|
293
|
+
html { overflow-x: hidden; }
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/* ===== PDP layout de produto ===== */
|
|
298
|
+
/* Tipografia: corpo em --font-sans, títulos/display em --font-display — ambos definidos
|
|
299
|
+
no bloco UNBOX-FONTS de app/layout.tsx (trocados por estilo/preset pelo CLI). */
|
|
300
|
+
.store-layout { font-family: var(--font-sans), system-ui, sans-serif; }
|
|
301
|
+
.font-display { font-family: var(--font-display), var(--font-sans), sans-serif; }
|
|
302
|
+
|
|
303
|
+
/* Títulos — usam a fonte de display do estilo */
|
|
304
|
+
h1, h2, h3, h4 {
|
|
305
|
+
font-family: var(--font-heading), var(--font-sans), system-ui, sans-serif;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/* Quebra do container restrito do <main> p/ seções full-bleed da PDP */
|
|
309
|
+
.full-bleed {
|
|
310
|
+
width: 100vw;
|
|
311
|
+
position: relative;
|
|
312
|
+
left: 50%;
|
|
313
|
+
right: 50%;
|
|
314
|
+
margin-left: -50vw;
|
|
315
|
+
margin-right: -50vw;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
@keyframes storePulse { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }
|
|
319
|
+
.animate-store-pulse { animation: storePulse 1.4s infinite; }
|
|
320
|
+
|
|
321
|
+
/* Card de seção — chrome controlado pelos eixos de composição (--card-*). O raio continua
|
|
322
|
+
vindo das classes rounded-* (escala do --radius). Use junto: "store-card rounded-2xl". */
|
|
323
|
+
.store-card {
|
|
324
|
+
background: var(--card-bg, #ffffff);
|
|
325
|
+
border: var(--card-border, 1px solid var(--store-line));
|
|
326
|
+
box-shadow: var(--card-shadow, none);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/* Marquee infinito (seção attributes-marquee) — N sets idênticos (N par), -50% fecha o loop.
|
|
330
|
+
A seção define a duração inline proporcional ao número de cópias. */
|
|
331
|
+
@keyframes marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
|
|
332
|
+
.animate-marquee { animation: marquee 22s linear infinite; }
|
|
333
|
+
@media (prefers-reduced-motion: reduce) { .animate-marquee { animation: none; } }
|
|
334
|
+
|
|
335
|
+
/* Chama "tremeluzindo" — usada nos ícones de foguinho (urgência/vendas). */
|
|
336
|
+
@keyframes flameFlicker {
|
|
337
|
+
0%, 100% { transform: scale(1) rotate(0deg); opacity: 1; }
|
|
338
|
+
25% { transform: scale(1.12) rotate(-4deg); opacity: .85; }
|
|
339
|
+
50% { transform: scale(.96) rotate(3deg); opacity: 1; }
|
|
340
|
+
75% { transform: scale(1.08) rotate(-2deg); opacity: .9; }
|
|
341
|
+
}
|
|
342
|
+
.flame { animation: flameFlicker 1.1s ease-in-out infinite; transform-origin: center bottom; }
|
|
343
|
+
@media (prefers-reduced-motion: reduce) { .flame { animation: none; } }
|
|
344
|
+
|
|
345
|
+
/* Checkout: chrome próprio (header simplificado, sem footer) ocupando a tela inteira.
|
|
346
|
+
SSR-safe via :has() — sem JS, sem mismatch de hidratação, sem flash. A página do
|
|
347
|
+
checkout marca seu container raiz com .checkout-root. */
|
|
348
|
+
body:has(.checkout-root) .site-chrome { display: none; }
|
|
349
|
+
body:has(.checkout-root) .site-main { max-width: none; padding: 0; min-height: 0; }
|
|
350
|
+
|
|
351
|
+
/* Páginas full-bleed (home, catálogo, PDP) começam coladas no header E no footer — remove o
|
|
352
|
+
padding do <main> e a margem própria do footer, senão sobra uma faixa da cor de fundo padrão
|
|
353
|
+
entre a seção full-bleed e o header/footer. */
|
|
354
|
+
.site-main:has(.full-bleed) { padding-top: 0; padding-bottom: 0; }
|
|
355
|
+
body:has(.site-main .full-bleed) footer.site-chrome { margin-top: 0; }
|
|
356
|
+
|
|
357
|
+
/* ── Header SOBREPOSTO (variante "imersivo" do chrome) ───────────────────────────────────
|
|
358
|
+
SÓLIDO é o default: a transparência é o ramo OPT-IN do :has() e exige acordo dos DOIS
|
|
359
|
+
lados — o hero marca `.hero-imersivo` E a receita escolheu o header imersivo
|
|
360
|
+
(data-chrome). Assim um header claro nunca fica transparente sobre foto por engano, e
|
|
361
|
+
qualquer página sem esse hero (catálogo, PDP, conta, busca) sai sólida.
|
|
362
|
+
Mesma mecânica do checkout acima: SSR-safe, sem JS, sem flash. Browser sem :has() →
|
|
363
|
+
header sólido, que é o modo seguro. */
|
|
364
|
+
header.site-chrome[data-chrome="imersivo"] { --chrome-h: 60px; }
|
|
365
|
+
@media (min-width: 768px) { header.site-chrome[data-chrome="imersivo"] { --chrome-h: 76px; } }
|
|
366
|
+
|
|
367
|
+
/* 1. layout: o header sai do fluxo (sem position:fixed, que o `overflow-x: clip` do html
|
|
368
|
+
pode clipar) e o hero sobe por baixo dele. */
|
|
369
|
+
body:has(.hero-imersivo) header.site-chrome[data-chrome="imersivo"] {
|
|
370
|
+
margin-bottom: calc(-1 * var(--chrome-h));
|
|
371
|
+
}
|
|
372
|
+
/* 2. pintura: véu na PRÓPRIA cor de chrome — logo e ícones legíveis pelo mesmo motivo que
|
|
373
|
+
já valem sobre o chrome sólido, sem asset novo. .chrome-bar ganha data-solid ao rolar. */
|
|
374
|
+
body:has(.hero-imersivo) header.site-chrome[data-chrome="imersivo"] .chrome-bar:not([data-solid]) {
|
|
375
|
+
background: linear-gradient(to bottom, color-mix(in srgb, var(--store-chrome-bg) 62%, transparent), transparent);
|
|
376
|
+
box-shadow: none;
|
|
377
|
+
}
|
|
378
|
+
@media (prefers-reduced-motion: reduce) { .chrome-bar { transition: none; } }
|
|
379
|
+
|
|
380
|
+
/* Âncoras (#destaques, #comprar) não somem sob o header sticky — vale para as 4 variantes. */
|
|
381
|
+
html { scroll-padding-top: var(--chrome-h, 88px); }
|
|
382
|
+
|
|
383
|
+
/* HTML rico sanitizado (descrição/infos do produto) */
|
|
384
|
+
.richtext {
|
|
385
|
+
@apply text-sm leading-relaxed text-foreground/90;
|
|
386
|
+
}
|
|
387
|
+
.richtext p { @apply my-2; }
|
|
388
|
+
.richtext ul { @apply my-2 list-disc pl-5; }
|
|
389
|
+
.richtext ol { @apply my-2 list-decimal pl-5; }
|
|
390
|
+
.richtext h1, .richtext h2, .richtext h3, .richtext h4 { @apply mt-4 mb-2 font-semibold; }
|
|
391
|
+
.richtext a { @apply text-primary underline underline-offset-2; }
|
|
392
|
+
.richtext img { @apply my-2 max-w-full rounded-md; }
|
|
393
|
+
.richtext table { @apply my-3 w-full border-collapse text-sm; }
|
|
394
|
+
.richtext th, .richtext td { @apply border border-border px-2 py-1 text-left; }
|
|
395
|
+
|
|
396
|
+
/* ── MOTION ──────────────────────────────────────────────────────────────────────────────
|
|
397
|
+
CSS PURO, zero JavaScript: as animações usam timeline de scroll (`animation-timeline:
|
|
398
|
+
view()`), que roda no compositor. Não entra um byte no bundle, não há hidratação e não
|
|
399
|
+
existe estado "invisível esperando script" — por isso não afeta o tempo da primeira tela.
|
|
400
|
+
|
|
401
|
+
Navegador sem suporte (Firefox hoje) simplesmente NÃO anima: o conteúdo aparece normal.
|
|
402
|
+
A degradação é sempre pro lado seguro, então o default de tudo é VISÍVEL.
|
|
403
|
+
|
|
404
|
+
⚠️ REGRA DURA: nada da primeira dobra anima. O hero nunca leva .reveal — conteúdo que
|
|
405
|
+
entra animado no topo atrasa a percepção de carregamento e mexe no LCP.
|
|
406
|
+
|
|
407
|
+
Intensidade vem do dial MOTION_INTENSITY do briefing, via --motion (0 = desligado). */
|
|
408
|
+
:root {
|
|
409
|
+
/* Declaração numa linha só, indentada: é o formato que o create-unbox-store procura
|
|
410
|
+
(regex ancorada em início de linha) pra escrever o valor do dial. Não colapse. */
|
|
411
|
+
--motion: 1;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
@supports (animation-timeline: view()) {
|
|
415
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
416
|
+
/* Aparição ao entrar na viewport. Só abaixo da dobra. */
|
|
417
|
+
.reveal {
|
|
418
|
+
animation: unbox-reveal linear both;
|
|
419
|
+
animation-timeline: view();
|
|
420
|
+
animation-range: entry 0% entry 45%;
|
|
421
|
+
}
|
|
422
|
+
/* Deslocamento lento da imagem dentro do próprio frame (parallax contido).
|
|
423
|
+
O pai precisa de overflow-hidden; a imagem usa scale pra não mostrar borda. */
|
|
424
|
+
.parallax-slow {
|
|
425
|
+
animation: unbox-parallax linear both;
|
|
426
|
+
animation-timeline: view();
|
|
427
|
+
animation-range: cover 0% cover 100%;
|
|
428
|
+
will-change: transform;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
@keyframes unbox-reveal {
|
|
434
|
+
from { opacity: 0; transform: translateY(calc(18px * var(--motion))); }
|
|
435
|
+
to { opacity: 1; transform: none; }
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
@keyframes unbox-parallax {
|
|
439
|
+
from { transform: scale(1.08) translateY(calc(-2.5% * var(--motion))); }
|
|
440
|
+
to { transform: scale(1.08) translateY(calc(2.5% * var(--motion))); }
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/* ── ACESSIBILIDADE: piso de 16px em campo de formulário no mobile ───────────────────────
|
|
444
|
+
O iOS dá zoom automático ao focar um input com fonte menor que 16px, e a "solução"
|
|
445
|
+
antiga era desligar o pinch-zoom no viewport — que quebra a WCAG 1.4.4 para todo mundo
|
|
446
|
+
para resolver um detalhe de uma plataforma. Esta é a correção certa.
|
|
447
|
+
|
|
448
|
+
!important é deliberado: utilitário do Tailwind (text-[.98rem] e afins) vence seletor de
|
|
449
|
+
elemento, e este é um piso de acessibilidade que não deve ser sobrescrito por engano. Só
|
|
450
|
+
vale abaixo de 768px, onde o problema existe. */
|
|
451
|
+
@media (max-width: 767px) {
|
|
452
|
+
input:not([type="checkbox"]):not([type="radio"]),
|
|
453
|
+
select,
|
|
454
|
+
textarea {
|
|
455
|
+
font-size: 16px !important;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Metadata, Viewport } from "next";
|
|
2
|
+
import "./globals.css";
|
|
3
|
+
import { Toaster } from "@/components/ui/sonner";
|
|
4
|
+
// EDITOR DE LOJA: o conteúdo publicado pelo lojista entra aqui e os primitivos o aplicam por cima do
|
|
5
|
+
// que está escrito no código. Sem EDITOR_URL no ambiente, `conteudo` é null e a loja renderiza
|
|
6
|
+
// exatamente o código: o editor fica desligado, sem efeito nenhum.
|
|
7
|
+
import { EditableProvider } from "@/lib/editable";
|
|
8
|
+
import { getPublishedContent, presencaNoAmbiente } from "@/lib/editable/server";
|
|
9
|
+
// RASTREIO E MARKETING: a foundation renderiza tudo (GTM da Unbox e do lojista, GA4, Meta Pixel, TikTok,
|
|
10
|
+
// Pinterest, page_view por rota, botão de WhatsApp) numa linha, `<Rastreio>`; este layout não conhece
|
|
11
|
+
// provedor nenhum, e o próximo entra por versão da foundation. Ela lê as MESMAS variáveis de sempre
|
|
12
|
+
// (NEXT_PUBLIC_GTM_ID, NEXT_PUBLIC_GA_ID, NEXT_PUBLIC_META_PIXEL_ID) com a mesma régua (placeholder e
|
|
13
|
+
// formato errado são ausência; Pixel só sem GTM próprio), então nenhuma loja perde rastreio.
|
|
14
|
+
import { Rastreio } from "@/lib/editable/rastreio";
|
|
15
|
+
import { EDITABLE_TOKENS } from "@/lib/editable/tokens";
|
|
16
|
+
import { EDITOR_ORIGIN, STORE_SLUG } from "@/lib/editable/config";
|
|
17
|
+
|
|
18
|
+
// UNBOX-FONTS-BEGIN (bloco reescrito pelo create-unbox-store conforme o estilo escolhido — não renomear os markers)
|
|
19
|
+
import { Geist_Mono, Poppins, Plus_Jakarta_Sans } from "next/font/google";
|
|
20
|
+
const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"] });
|
|
21
|
+
const sans = Plus_Jakarta_Sans({ variable: "--font-sans", subsets: ["latin"], weight: ["400", "500", "600", "700"], display: "swap" });
|
|
22
|
+
const displayFont = Poppins({ variable: "--font-display", subsets: ["latin"], weight: ["500", "600", "700", "800"], display: "swap" });
|
|
23
|
+
// UNBOX-FONTS-END
|
|
24
|
+
|
|
25
|
+
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL ?? "http://localhost:3000";
|
|
26
|
+
|
|
27
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
28
|
+
// GTM CENTRAL DA UNBOX — OBRIGATÓRIO EM TODA LOJA. NÃO REMOVA NEM TROQUE O ID.
|
|
29
|
+
// É o container central da Unbox para captura de dados da plataforma (parte do
|
|
30
|
+
// contrato da loja). Tags próprias da marca NÃO entram aqui: o lojista configura
|
|
31
|
+
// os IDs dele (GTM próprio, GA4, Meta Pixel, TikTok, Pinterest, WhatsApp) na aba
|
|
32
|
+
// Apps do painel do editor, ou o cadastro da loja os traz do ambiente
|
|
33
|
+
// (NEXT_PUBLIC_GTM_ID / NEXT_PUBLIC_GA_ID / NEXT_PUBLIC_META_PIXEL_ID …), e a
|
|
34
|
+
// foundation injeta (`<Rastreio>`, lib/editable/rastreio.tsx), nunca
|
|
35
|
+
// editando/substituindo este ID. Este literal vai para o `<Rastreio>` como o
|
|
36
|
+
// contêiner contratual, e o prebuild (scripts/check-unbox-brand.mjs) cobra que
|
|
37
|
+
// ele continue aqui.
|
|
38
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
39
|
+
const UNBOX_GTM_ID = "GTM-PZLT336";
|
|
40
|
+
|
|
41
|
+
export const metadata: Metadata = {
|
|
42
|
+
metadataBase: new URL(siteUrl),
|
|
43
|
+
title: {
|
|
44
|
+
default: "Minha Loja · Compre Online",
|
|
45
|
+
template: "%s · Minha Loja",
|
|
46
|
+
},
|
|
47
|
+
// ⚠ Descrição PROVISÓRIA: não diz o que a loja vende. O briefing troca por uma frase com o
|
|
48
|
+
// produto e o público — é o que busca e resposta de IA extraem. O build avisa enquanto for esta.
|
|
49
|
+
// Sem description de fábrica, de propósito: "Produtos de qualidade entregues na sua casa"
|
|
50
|
+
// PARECE preenchido e ninguém revisa. Vazio o build avisa e o Google gera a partir do
|
|
51
|
+
// conteúdo, que é melhor que uma frase que não diz o que a loja vende. O briefing escreve.
|
|
52
|
+
description: process.env.NEXT_PUBLIC_SITE_DESCRIPTION || undefined,
|
|
53
|
+
// openGraph.images vem de app/opengraph-image.tsx (gerada com o nome da loja nas cores da
|
|
54
|
+
// marca): todo compartilhamento sai com imagem, mesmo antes de a marca mandar uma foto.
|
|
55
|
+
openGraph: { type: "website", locale: "pt_BR", siteName: "Minha Loja" },
|
|
56
|
+
twitter: { card: "summary_large_image" },
|
|
57
|
+
// Favicon: NÃO declarar `icons` aqui — app/icon.svg e app/apple-icon.svg (convenção do
|
|
58
|
+
// App Router) já geram os <link> corretos. Apontar pro logo horizontal deixa o favicon
|
|
59
|
+
// ilegível/invisível na aba. Personalize substituindo esses dois arquivos.
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// NÃO adicione maximumScale/userScalable aqui. Bloquear o pinch-zoom viola a WCAG 1.4.4
|
|
63
|
+
// (Resize Text) e prejudica de verdade quem depende de ampliar a tela — e toda loja gerada
|
|
64
|
+
// herdava isso. O motivo original era o zoom automático do iOS ao focar um input, que
|
|
65
|
+
// acontece quando o campo tem fonte MENOR que 16px; a correção certa é o piso de 16px nos
|
|
66
|
+
// controles de formulário no mobile, que está em app/globals.css.
|
|
67
|
+
export const viewport: Viewport = {
|
|
68
|
+
width: "device-width",
|
|
69
|
+
initialScale: 1,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export default async function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
|
73
|
+
const conteudo = await getPublishedContent();
|
|
74
|
+
return (
|
|
75
|
+
<html lang="pt-BR">
|
|
76
|
+
<body className={`${sans.variable} ${geistMono.variable} ${displayFont.variable} antialiased`}>
|
|
77
|
+
{/* Header/rodapé/nav da loja NÃO ficam aqui: moram em app/(loja)/layout.tsx (route
|
|
78
|
+
group). Página criada fora de (loja) — acesso, erro, landing — nasce sem chrome. */}
|
|
79
|
+
<EditableProvider doc={conteudo} shop={STORE_SLUG} tokens={EDITABLE_TOKENS} editorOrigin={EDITOR_ORIGIN || undefined} apps={presencaNoAmbiente(process.env, { unboxGtmId: UNBOX_GTM_ID })}>
|
|
80
|
+
{children}
|
|
81
|
+
</EditableProvider>
|
|
82
|
+
<Toaster position="top-center" />
|
|
83
|
+
{/* rastreio e marketing: o que o lojista publicou na aba Apps vence o ambiente, provedor a provedor,
|
|
84
|
+
e cada provedor dispara UMA vez; o contêiner da Unbox entra sempre. Só muda quando ele PUBLICA.
|
|
85
|
+
Nada de script de GA/Pixel/GTM escrito à mão neste arquivo: seria o mesmo provedor duas vezes. */}
|
|
86
|
+
<Rastreio doc={conteudo} ambiente={process.env} unboxGtmId={UNBOX_GTM_ID} />
|
|
87
|
+
</body>
|
|
88
|
+
</html>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// /llms.txt — apresenta a loja a modelos de IA e agentes.
|
|
2
|
+
//
|
|
3
|
+
// É uma ROTA, não um arquivo em public/, pelo mesmo motivo do resto do template: o que a loja
|
|
4
|
+
// afirma vem do catálogo real. Arquivo estático nasce desatualizado no dia em que um produto sai
|
|
5
|
+
// de linha, e o formato estático já foi ao ar com "TODO: preencher" numa loja de produção,
|
|
6
|
+
// servindo isso justamente aos crawlers que o robots.txt convida.
|
|
7
|
+
//
|
|
8
|
+
// O formato mora em lib/llms-txt.ts (função pura, testável). Aqui só buscamos os dados.
|
|
9
|
+
import { loadAllCatalogItems } from "@/lib/dataloader";
|
|
10
|
+
import { getTopTags } from "@/lib/queries";
|
|
11
|
+
import { buildTagMap, mapCatalogItems } from "@/lib/catalog-map";
|
|
12
|
+
import { montarLlmsTxt } from "@/lib/llms-txt";
|
|
13
|
+
import { FREE_SHIPPING_THRESHOLD, PIX_DISCOUNT_PCT } from "@/lib/store-config";
|
|
14
|
+
|
|
15
|
+
export const revalidate = 3600;
|
|
16
|
+
|
|
17
|
+
export async function GET() {
|
|
18
|
+
// Falha de API não pode derrubar o arquivo: sem catálogo ele sai com as rotas e as políticas,
|
|
19
|
+
// que continuam verdadeiras.
|
|
20
|
+
const [itens, tags] = await Promise.all([
|
|
21
|
+
loadAllCatalogItems().catch(() => [] as any[]),
|
|
22
|
+
getTopTags().catch(() => [] as any[]),
|
|
23
|
+
]);
|
|
24
|
+
const produtos = mapCatalogItems(itens as any[], buildTagMap(tags as any[]));
|
|
25
|
+
|
|
26
|
+
const texto = montarLlmsTxt(produtos, {
|
|
27
|
+
siteName: process.env.NEXT_PUBLIC_SITE_NAME || "Minha Loja",
|
|
28
|
+
siteUrl: process.env.NEXT_PUBLIC_SITE_URL,
|
|
29
|
+
descricao: process.env.NEXT_PUBLIC_SITE_DESCRIPTION,
|
|
30
|
+
categorias: (tags as any[])
|
|
31
|
+
.filter((t) => t?.slug && t.isVisible !== false)
|
|
32
|
+
.map((t) => ({ nome: t.displayTitle || t.name, slug: t.slug })),
|
|
33
|
+
pixDescontoPct: PIX_DISCOUNT_PCT,
|
|
34
|
+
freteGratisAcimaDe: FREE_SHIPPING_THRESHOLD,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return new Response(texto, {
|
|
38
|
+
headers: {
|
|
39
|
+
"content-type": "text/plain; charset=utf-8",
|
|
40
|
+
"cache-control": "public, max-age=3600, s-maxage=3600, stale-while-revalidate=86400",
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { MetadataRoute } from "next";
|
|
2
|
+
|
|
3
|
+
export default function manifest(): MetadataRoute.Manifest {
|
|
4
|
+
return {
|
|
5
|
+
name: process.env.NEXT_PUBLIC_SITE_NAME ?? "Minha Loja",
|
|
6
|
+
short_name: process.env.NEXT_PUBLIC_SITE_NAME ?? "Loja",
|
|
7
|
+
description: "Loja oficial. Compre com segurança e receba em casa.",
|
|
8
|
+
start_url: "/",
|
|
9
|
+
display: "standalone",
|
|
10
|
+
background_color: "#ffffff",
|
|
11
|
+
// theme_color: usar o --store-chrome-bg da marca (preencher ao configurar cores)
|
|
12
|
+
theme_color: "#18181B",
|
|
13
|
+
orientation: "portrait",
|
|
14
|
+
icons: [
|
|
15
|
+
// SVG funciona na maioria dos browsers modernos
|
|
16
|
+
{ src: "/brand/logo.svg", sizes: "any", type: "image/svg+xml" },
|
|
17
|
+
// TODO: criar /public/icon-192.png e /public/icon-512.png com a marca real
|
|
18
|
+
// (PNG é necessário para Android e iOS antigos; usar squoosh.app ou similar)
|
|
19
|
+
],
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { Button } from "@/components/ui/button";
|
|
3
|
+
|
|
4
|
+
export default function NotFound() {
|
|
5
|
+
return (
|
|
6
|
+
<div className="flex flex-col items-center justify-center py-24 text-center">
|
|
7
|
+
<p className="text-5xl font-bold text-primary">404</p>
|
|
8
|
+
<h1 className="mt-3 text-xl font-semibold">Página não encontrada</h1>
|
|
9
|
+
<p className="mt-1 max-w-sm text-sm text-muted-foreground">
|
|
10
|
+
O produto pode ter saído do catálogo ou o endereço está incorreto.
|
|
11
|
+
</p>
|
|
12
|
+
<div className="mt-6 flex gap-2">
|
|
13
|
+
<Button nativeButton={false} render={<Link href="/" />}>Voltar ao início</Button>
|
|
14
|
+
<Button variant="outline" nativeButton={false} render={<Link href="/produtos" />}>Ver produtos</Button>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|