@cimplify/cli 0.2.8 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{add-7PTWJV4F.mjs → add-ZJNXWN2B.mjs} +10 -10
- package/dist/assets-DMK2QOPD.mjs +208 -0
- package/dist/chunk-5IAYN7AJ.mjs +259 -0
- package/dist/{chunk-4SBJVRGM.mjs → chunk-C4M3DXKC.mjs} +3 -1
- package/dist/{chunk-NC3GKHDD.mjs → chunk-D7WMSGKK.mjs} +1 -1
- package/dist/{chunk-NZ4RG62Z.mjs → chunk-I3XQSSOT.mjs} +4 -1
- package/dist/{chunk-JJYWETGA.mjs → chunk-LS2VTSMQ.mjs} +8 -2
- package/dist/{chunk-H2HJQGFY.mjs → chunk-MHK4WVNF.mjs} +2392 -596
- package/dist/{chunk-JOUXICGV.mjs → chunk-MOZQODQS.mjs} +1 -1
- package/dist/{chunk-KPGRCXQY.mjs → chunk-QGBXGDA5.mjs} +5 -5
- package/dist/chunk-RRY3NEZZ.mjs +79 -0
- package/dist/{chunk-L6474RPL.mjs → chunk-RZQTHTXX.mjs} +1 -1
- package/dist/{chunk-4YSOZ6LY.mjs → chunk-YI7UMMM7.mjs} +1 -1
- package/dist/{chunk-UPEHLREA.mjs → chunk-YQVMG62Z.mjs} +3 -3
- package/dist/{deploy-6KVOROT3.mjs → deploy-UKOOPJAE.mjs} +8 -82
- package/dist/{dev-AQP6TMYK.mjs → dev-FD4PM3UD.mjs} +5 -5
- package/dist/dispatcher.mjs +34 -22
- package/dist/doctor-5LBLYT7M.mjs +314 -0
- package/dist/{domains-2ZQ7AG27.mjs → domains-JQMV6GAP.mjs} +5 -5
- package/dist/{env-FDBPGU3W.mjs → env-EVMYQUIK.mjs} +6 -6
- package/dist/explain-3KBMWL6M.mjs +223 -0
- package/dist/introspect-PFBI3JHO.mjs +8 -0
- package/dist/{link-P4K2HRXY.mjs → link-X3E4UZBF.mjs} +4 -4
- package/dist/{list-44MLIFI2.mjs → list-TE54SJIB.mjs} +3 -3
- package/dist/{login-RSKGT6GU.mjs → login-WSAW4BEA.mjs} +4 -4
- package/dist/{logout-ZFZLSJ32.mjs → logout-DJDINVDF.mjs} +2 -2
- package/dist/{logs-E2AGTDCF.mjs → logs-KUKGEXR2.mjs} +4 -4
- package/dist/{projects-5CJOZ3MT.mjs → projects-364HGWHO.mjs} +13 -11
- package/dist/repo-26N2CHF6.mjs +8 -0
- package/dist/{rollback-36O4NOEL.mjs → rollback-5YALPQXL.mjs} +5 -5
- package/dist/{status-6AT4HF63.mjs → status-W4HW3CX3.mjs} +4 -4
- package/dist/{unlink-5ABCT7B6.mjs → unlink-HIIW57OO.mjs} +2 -2
- package/dist/{update-6KEG7EWK.mjs → update-5MRKRVZC.mjs} +7 -7
- package/dist/{whoami-DIJZYZIN.mjs → whoami-LACWBSNL.mjs} +3 -3
- package/package.json +3 -3
- package/templates/storefront-auto/.claude/skills/cimplify-storefront/SKILL.md +145 -0
- package/templates/storefront-auto/.cursor/rules/cimplify-storefront.mdc +25 -0
- package/templates/storefront-auto/.env.example +22 -0
- package/templates/storefront-auto/AGENTS.md +95 -0
- package/templates/storefront-auto/CLAUDE.md +22 -0
- package/templates/storefront-auto/README.md +48 -0
- package/templates/storefront-auto/__tests__/brand.test.ts +4 -0
- package/templates/storefront-auto/__tests__/cart-flow.test.ts +4 -0
- package/templates/storefront-auto/__tests__/contract.test.ts +4 -0
- package/templates/storefront-auto/app/.well-known/ucp/route.ts +65 -0
- package/templates/storefront-auto/app/about/page.tsx +41 -0
- package/templates/storefront-auto/app/accessibility/page.tsx +11 -0
- package/templates/storefront-auto/app/account/addresses/page.tsx +21 -0
- package/templates/storefront-auto/app/account/orders/page.tsx +21 -0
- package/templates/storefront-auto/app/account/page.tsx +22 -0
- package/templates/storefront-auto/app/account/settings/page.tsx +21 -0
- package/templates/storefront-auto/app/cart/page.tsx +9 -0
- package/templates/storefront-auto/app/categories/[slug]/listing-client.tsx +19 -0
- package/templates/storefront-auto/app/categories/[slug]/page.tsx +130 -0
- package/templates/storefront-auto/app/checkout/page.tsx +17 -0
- package/templates/storefront-auto/app/collections/[slug]/listing-client.tsx +20 -0
- package/templates/storefront-auto/app/collections/[slug]/page.tsx +130 -0
- package/templates/storefront-auto/app/contact/contact-form.tsx +109 -0
- package/templates/storefront-auto/app/contact/page.tsx +54 -0
- package/templates/storefront-auto/app/error.tsx +61 -0
- package/templates/storefront-auto/app/faq/page.tsx +46 -0
- package/templates/storefront-auto/app/globals.css +47 -0
- package/templates/storefront-auto/app/layout.tsx +77 -0
- package/templates/storefront-auto/app/llms.txt/route.ts +94 -0
- package/templates/storefront-auto/app/login/page.tsx +17 -0
- package/templates/storefront-auto/app/not-found.tsx +39 -0
- package/templates/storefront-auto/app/opensearch.xml/route.ts +37 -0
- package/templates/storefront-auto/app/orders/[id]/page.tsx +24 -0
- package/templates/storefront-auto/app/page.tsx +94 -0
- package/templates/storefront-auto/app/privacy/page.tsx +44 -0
- package/templates/storefront-auto/app/products/[slug]/page.tsx +165 -0
- package/templates/storefront-auto/app/products/[slug]/product-detail.tsx +70 -0
- package/templates/storefront-auto/app/returns/page.tsx +11 -0
- package/templates/storefront-auto/app/robots.ts +18 -0
- package/templates/storefront-auto/app/search/page.tsx +38 -0
- package/templates/storefront-auto/app/search/search-client.tsx +7 -0
- package/templates/storefront-auto/app/shipping/page.tsx +16 -0
- package/templates/storefront-auto/app/shop/page.tsx +63 -0
- package/templates/storefront-auto/app/shop/shop-client.tsx +32 -0
- package/templates/storefront-auto/app/signup/page.tsx +17 -0
- package/templates/storefront-auto/app/sitemap-page/page.tsx +167 -0
- package/templates/storefront-auto/app/sitemap.ts +59 -0
- package/templates/storefront-auto/app/terms/page.tsx +44 -0
- package/templates/storefront-auto/app/track-order/page.tsx +24 -0
- package/templates/storefront-auto/app/track-order/track-order-form.tsx +69 -0
- package/templates/storefront-auto/components/account-iframe.tsx +13 -0
- package/templates/storefront-auto/components/auto-hero.tsx +85 -0
- package/templates/storefront-auto/components/brand-marquee.tsx +27 -0
- package/templates/storefront-auto/components/cart-drawer.tsx +14 -0
- package/templates/storefront-auto/components/cart-pill.tsx +36 -0
- package/templates/storefront-auto/components/category-grid.tsx +28 -0
- package/templates/storefront-auto/components/category-tiles.tsx +104 -0
- package/templates/storefront-auto/components/collection-strip.tsx +45 -0
- package/templates/storefront-auto/components/feature-hero.tsx +84 -0
- package/templates/storefront-auto/components/fitment-finder.tsx +184 -0
- package/templates/storefront-auto/components/footer.tsx +153 -0
- package/templates/storefront-auto/components/header.tsx +45 -0
- package/templates/storefront-auto/components/hero.tsx +28 -0
- package/templates/storefront-auto/components/nav-link.tsx +20 -0
- package/templates/storefront-auto/components/newsletter.tsx +50 -0
- package/templates/storefront-auto/components/policy-page.tsx +49 -0
- package/templates/storefront-auto/components/promo-banner.tsx +41 -0
- package/templates/storefront-auto/components/providers.tsx +35 -0
- package/templates/storefront-auto/components/section-heading.tsx +37 -0
- package/templates/storefront-auto/components/service-brief.tsx +65 -0
- package/templates/storefront-auto/components/store-product-card.tsx +88 -0
- package/templates/storefront-auto/components/trade-in-cta.tsx +54 -0
- package/templates/storefront-auto/components/trust-bar.tsx +66 -0
- package/templates/storefront-auto/lib/brand.ts +744 -0
- package/templates/storefront-auto/lib/cart.ts +12 -0
- package/templates/storefront-auto/lib/cimplify-loader.ts +19 -0
- package/templates/storefront-auto/next.config.ts +45 -0
- package/templates/storefront-auto/package.json +35 -0
- package/templates/storefront-auto/postcss.config.mjs +7 -0
- package/templates/storefront-auto/tsconfig.json +23 -0
- package/templates/storefront-auto/vitest.config.ts +9 -0
- package/templates/storefront-bakery/.env.example +2 -2
- package/templates/storefront-bakery/README.md +1 -1
- package/templates/storefront-bakery/lib/cimplify-loader.ts +19 -0
- package/templates/storefront-bakery/next.config.ts +3 -0
- package/templates/storefront-bakery/package.json +1 -1
- package/templates/storefront-fashion/.env.example +2 -2
- package/templates/storefront-fashion/README.md +1 -1
- package/templates/storefront-fashion/lib/cimplify-loader.ts +19 -0
- package/templates/storefront-fashion/next.config.ts +3 -0
- package/templates/storefront-fashion/package.json +1 -1
- package/templates/storefront-grocery/.env.example +2 -2
- package/templates/storefront-grocery/README.md +1 -1
- package/templates/storefront-grocery/lib/cimplify-loader.ts +19 -0
- package/templates/storefront-grocery/next.config.ts +3 -0
- package/templates/storefront-grocery/package.json +1 -1
- package/templates/storefront-pharmacy/.claude/skills/cimplify-storefront/SKILL.md +145 -0
- package/templates/storefront-pharmacy/.cursor/rules/cimplify-storefront.mdc +25 -0
- package/templates/storefront-pharmacy/.env.example +22 -0
- package/templates/storefront-pharmacy/AGENTS.md +118 -0
- package/templates/storefront-pharmacy/CLAUDE.md +22 -0
- package/templates/storefront-pharmacy/README.md +87 -0
- package/templates/storefront-pharmacy/__tests__/brand.test.ts +4 -0
- package/templates/storefront-pharmacy/__tests__/cart-flow.test.ts +4 -0
- package/templates/storefront-pharmacy/__tests__/contract.test.ts +4 -0
- package/templates/storefront-pharmacy/app/.well-known/ucp/route.ts +65 -0
- package/templates/storefront-pharmacy/app/about/page.tsx +41 -0
- package/templates/storefront-pharmacy/app/accessibility/page.tsx +11 -0
- package/templates/storefront-pharmacy/app/account/addresses/page.tsx +21 -0
- package/templates/storefront-pharmacy/app/account/orders/page.tsx +21 -0
- package/templates/storefront-pharmacy/app/account/page.tsx +22 -0
- package/templates/storefront-pharmacy/app/account/settings/page.tsx +21 -0
- package/templates/storefront-pharmacy/app/cart/page.tsx +9 -0
- package/templates/storefront-pharmacy/app/categories/[slug]/listing-client.tsx +19 -0
- package/templates/storefront-pharmacy/app/categories/[slug]/page.tsx +130 -0
- package/templates/storefront-pharmacy/app/checkout/page.tsx +17 -0
- package/templates/storefront-pharmacy/app/collections/[slug]/listing-client.tsx +20 -0
- package/templates/storefront-pharmacy/app/collections/[slug]/page.tsx +130 -0
- package/templates/storefront-pharmacy/app/contact/contact-form.tsx +109 -0
- package/templates/storefront-pharmacy/app/contact/page.tsx +54 -0
- package/templates/storefront-pharmacy/app/error.tsx +61 -0
- package/templates/storefront-pharmacy/app/faq/page.tsx +46 -0
- package/templates/storefront-pharmacy/app/globals.css +47 -0
- package/templates/storefront-pharmacy/app/layout.tsx +77 -0
- package/templates/storefront-pharmacy/app/llms.txt/route.ts +94 -0
- package/templates/storefront-pharmacy/app/login/page.tsx +17 -0
- package/templates/storefront-pharmacy/app/not-found.tsx +39 -0
- package/templates/storefront-pharmacy/app/opensearch.xml/route.ts +37 -0
- package/templates/storefront-pharmacy/app/orders/[id]/page.tsx +24 -0
- package/templates/storefront-pharmacy/app/page.tsx +78 -0
- package/templates/storefront-pharmacy/app/privacy/page.tsx +44 -0
- package/templates/storefront-pharmacy/app/products/[slug]/page.tsx +165 -0
- package/templates/storefront-pharmacy/app/products/[slug]/product-detail.tsx +70 -0
- package/templates/storefront-pharmacy/app/returns/page.tsx +11 -0
- package/templates/storefront-pharmacy/app/robots.ts +18 -0
- package/templates/storefront-pharmacy/app/search/page.tsx +38 -0
- package/templates/storefront-pharmacy/app/search/search-client.tsx +7 -0
- package/templates/storefront-pharmacy/app/shipping/page.tsx +16 -0
- package/templates/storefront-pharmacy/app/shop/page.tsx +63 -0
- package/templates/storefront-pharmacy/app/shop/shop-client.tsx +32 -0
- package/templates/storefront-pharmacy/app/signup/page.tsx +17 -0
- package/templates/storefront-pharmacy/app/sitemap-page/page.tsx +167 -0
- package/templates/storefront-pharmacy/app/sitemap.ts +59 -0
- package/templates/storefront-pharmacy/app/terms/page.tsx +44 -0
- package/templates/storefront-pharmacy/app/track-order/page.tsx +24 -0
- package/templates/storefront-pharmacy/app/track-order/track-order-form.tsx +69 -0
- package/templates/storefront-pharmacy/components/account-iframe.tsx +13 -0
- package/templates/storefront-pharmacy/components/brand-marquee.tsx +27 -0
- package/templates/storefront-pharmacy/components/cart-drawer.tsx +14 -0
- package/templates/storefront-pharmacy/components/cart-pill.tsx +36 -0
- package/templates/storefront-pharmacy/components/category-grid.tsx +28 -0
- package/templates/storefront-pharmacy/components/category-tiles.tsx +104 -0
- package/templates/storefront-pharmacy/components/collection-strip.tsx +45 -0
- package/templates/storefront-pharmacy/components/feature-hero.tsx +84 -0
- package/templates/storefront-pharmacy/components/footer.tsx +153 -0
- package/templates/storefront-pharmacy/components/header.tsx +45 -0
- package/templates/storefront-pharmacy/components/health-brief.tsx +65 -0
- package/templates/storefront-pharmacy/components/hero.tsx +28 -0
- package/templates/storefront-pharmacy/components/nav-link.tsx +20 -0
- package/templates/storefront-pharmacy/components/newsletter.tsx +50 -0
- package/templates/storefront-pharmacy/components/pharmacy-hero.tsx +95 -0
- package/templates/storefront-pharmacy/components/policy-page.tsx +49 -0
- package/templates/storefront-pharmacy/components/promo-banner.tsx +41 -0
- package/templates/storefront-pharmacy/components/providers.tsx +35 -0
- package/templates/storefront-pharmacy/components/section-heading.tsx +37 -0
- package/templates/storefront-pharmacy/components/store-product-card.tsx +88 -0
- package/templates/storefront-pharmacy/components/symptom-finder.tsx +108 -0
- package/templates/storefront-pharmacy/components/trade-in-cta.tsx +54 -0
- package/templates/storefront-pharmacy/components/trust-bar.tsx +66 -0
- package/templates/storefront-pharmacy/components/urgent-ctas.tsx +117 -0
- package/templates/storefront-pharmacy/lib/brand.ts +790 -0
- package/templates/storefront-pharmacy/lib/cart.ts +12 -0
- package/templates/storefront-pharmacy/lib/cimplify-loader.ts +19 -0
- package/templates/storefront-pharmacy/next.config.ts +45 -0
- package/templates/storefront-pharmacy/package.json +35 -0
- package/templates/storefront-pharmacy/postcss.config.mjs +7 -0
- package/templates/storefront-pharmacy/tsconfig.json +23 -0
- package/templates/storefront-pharmacy/vitest.config.ts +9 -0
- package/templates/storefront-restaurant/.env.example +2 -2
- package/templates/storefront-restaurant/README.md +1 -1
- package/templates/storefront-restaurant/lib/cimplify-loader.ts +19 -0
- package/templates/storefront-restaurant/next.config.ts +3 -0
- package/templates/storefront-restaurant/package.json +1 -1
- package/templates/storefront-retail/.env.example +2 -2
- package/templates/storefront-retail/README.md +1 -1
- package/templates/storefront-retail/lib/cimplify-loader.ts +19 -0
- package/templates/storefront-retail/next.config.ts +3 -0
- package/templates/storefront-retail/package.json +1 -1
- package/templates/storefront-services/.env.example +2 -2
- package/templates/storefront-services/README.md +1 -1
- package/templates/storefront-services/lib/cimplify-loader.ts +19 -0
- package/templates/storefront-services/next.config.ts +3 -0
- package/templates/storefront-services/package.json +1 -1
- package/dist/repo-E6SBKVDG.mjs +0 -8
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import Image from "next/image";
|
|
3
|
+
|
|
4
|
+
interface FeatureHeroProps {
|
|
5
|
+
eyebrow: string;
|
|
6
|
+
title: React.ReactNode;
|
|
7
|
+
description: string;
|
|
8
|
+
primaryCta: { label: string; href: string };
|
|
9
|
+
secondaryCta?: { label: string; href: string };
|
|
10
|
+
imageUrl: string;
|
|
11
|
+
imageAlt: string;
|
|
12
|
+
badge?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Apple-style split hero. Left: copy + CTAs. Right: large product image.
|
|
17
|
+
* Stacks to image-on-top on mobile. Strings come from `brand.hero` at the
|
|
18
|
+
* call site — this component is otherwise design-only.
|
|
19
|
+
*/
|
|
20
|
+
export function FeatureHero({
|
|
21
|
+
eyebrow,
|
|
22
|
+
title,
|
|
23
|
+
description,
|
|
24
|
+
primaryCta,
|
|
25
|
+
secondaryCta,
|
|
26
|
+
imageUrl,
|
|
27
|
+
imageAlt,
|
|
28
|
+
badge,
|
|
29
|
+
}: FeatureHeroProps) {
|
|
30
|
+
return (
|
|
31
|
+
<section className="relative overflow-hidden bg-foreground text-background">
|
|
32
|
+
<div className="absolute inset-0 opacity-[0.04] pointer-events-none [background-image:radial-gradient(circle_at_2px_2px,white_1px,transparent_0)] [background-size:40px_40px]" />
|
|
33
|
+
<div className="relative max-w-7xl mx-auto px-6 sm:px-8 py-16 sm:py-20 lg:py-24 grid grid-cols-1 lg:grid-cols-[1.1fr_1fr] gap-10 lg:gap-16 items-center">
|
|
34
|
+
<div>
|
|
35
|
+
{badge && (
|
|
36
|
+
<span className="inline-flex items-center gap-2 mb-5 px-3 py-1.5 rounded-full bg-primary/15 border border-primary/40 text-primary-foreground/95 text-[11px] font-mono uppercase tracking-[0.16em]">
|
|
37
|
+
<span className="grid place-items-center w-1.5 h-1.5 rounded-full bg-primary animate-pulse" />
|
|
38
|
+
{badge}
|
|
39
|
+
</span>
|
|
40
|
+
)}
|
|
41
|
+
<p className="text-[12px] font-mono uppercase tracking-[0.2em] text-background/60 mb-3">
|
|
42
|
+
{eyebrow}
|
|
43
|
+
</p>
|
|
44
|
+
<h1 className="text-[clamp(2.5rem,6vw,4.75rem)] font-bold m-0 mb-5 -tracking-[0.035em] leading-[1.02]">
|
|
45
|
+
{title}
|
|
46
|
+
</h1>
|
|
47
|
+
<p className="text-base sm:text-lg text-background/75 leading-relaxed max-w-xl">
|
|
48
|
+
{description}
|
|
49
|
+
</p>
|
|
50
|
+
<div className="flex flex-wrap items-center gap-3 mt-7">
|
|
51
|
+
<Link
|
|
52
|
+
href={primaryCta.href}
|
|
53
|
+
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-md bg-primary text-primary-foreground font-semibold text-sm hover:bg-primary/90 transition-colors"
|
|
54
|
+
>
|
|
55
|
+
{primaryCta.label}
|
|
56
|
+
<svg viewBox="0 0 12 12" className="w-3 h-3" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden>
|
|
57
|
+
<path d="M3 6h7m0 0L7 3m3 3L7 9" strokeLinecap="round" strokeLinejoin="round" />
|
|
58
|
+
</svg>
|
|
59
|
+
</Link>
|
|
60
|
+
{secondaryCta && (
|
|
61
|
+
<Link
|
|
62
|
+
href={secondaryCta.href}
|
|
63
|
+
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-md border border-background/25 text-background hover:bg-background/10 transition-colors text-sm font-medium"
|
|
64
|
+
>
|
|
65
|
+
{secondaryCta.label}
|
|
66
|
+
</Link>
|
|
67
|
+
)}
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
<div className="relative aspect-[4/3] lg:aspect-square w-full rounded-2xl overflow-hidden bg-background/5 ring-1 ring-background/10">
|
|
71
|
+
<Image
|
|
72
|
+
src={imageUrl}
|
|
73
|
+
alt={imageAlt}
|
|
74
|
+
fill
|
|
75
|
+
sizes="(min-width: 1024px) 50vw, 100vw"
|
|
76
|
+
className="object-cover"
|
|
77
|
+
priority
|
|
78
|
+
/>
|
|
79
|
+
<div className="absolute inset-0 bg-gradient-to-tr from-foreground/40 via-transparent to-transparent pointer-events-none" />
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</section>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { brand } from "@/lib/brand";
|
|
3
|
+
|
|
4
|
+
const ICONS: Record<string, React.ReactNode> = {
|
|
5
|
+
instagram: (
|
|
6
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" aria-hidden className="w-5 h-5">
|
|
7
|
+
<rect x="3" y="3" width="18" height="18" rx="5" />
|
|
8
|
+
<circle cx="12" cy="12" r="4" />
|
|
9
|
+
<circle cx="17.5" cy="6.5" r="0.75" fill="currentColor" />
|
|
10
|
+
</svg>
|
|
11
|
+
),
|
|
12
|
+
x: (
|
|
13
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden className="w-5 h-5">
|
|
14
|
+
<path d="M18 2h3l-7.5 8.6L22 22h-6.6l-5-6.5L4 22H1l8-9.2L1.4 2H8l4.6 6 5.4-6z" />
|
|
15
|
+
</svg>
|
|
16
|
+
),
|
|
17
|
+
tiktok: (
|
|
18
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden className="w-5 h-5">
|
|
19
|
+
<path d="M16 3v3.5a4.5 4.5 0 0 0 4.5 4.5V14a7.5 7.5 0 0 1-4.5-1.5V16a5 5 0 1 1-5-5v3a2 2 0 1 0 2 2V3z" />
|
|
20
|
+
</svg>
|
|
21
|
+
),
|
|
22
|
+
facebook: (
|
|
23
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden className="w-5 h-5">
|
|
24
|
+
<path d="M14 9V7a1 1 0 0 1 1-1h2V3h-3a4 4 0 0 0-4 4v2H8v3h2v9h3v-9h2.5l.5-3H13z" />
|
|
25
|
+
</svg>
|
|
26
|
+
),
|
|
27
|
+
youtube: (
|
|
28
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden className="w-5 h-5">
|
|
29
|
+
<path d="M22.5 6.5a2.6 2.6 0 0 0-1.8-1.8C19 4.2 12 4.2 12 4.2s-7 0-8.7.5A2.6 2.6 0 0 0 1.5 6.5C1 8.2 1 12 1 12s0 3.8.5 5.5a2.6 2.6 0 0 0 1.8 1.8C5 19.8 12 19.8 12 19.8s7 0 8.7-.5a2.6 2.6 0 0 0 1.8-1.8c.5-1.7.5-5.5.5-5.5s0-3.8-.5-5.5zM10 15.5v-7l6 3.5-6 3.5z" />
|
|
30
|
+
</svg>
|
|
31
|
+
),
|
|
32
|
+
linkedin: (
|
|
33
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden className="w-5 h-5">
|
|
34
|
+
<path d="M4 4h4v16H4zM6 2.5a2 2 0 1 0 0 4 2 2 0 0 0 0-4zM10 8h4v2.5h.1c.6-1.1 2-2.5 4-2.5 4 0 4.9 2.6 4.9 6V20h-4v-5c0-1.5-.5-3-2.3-3-1.7 0-2.5 1.3-2.5 3v5h-4z" />
|
|
35
|
+
</svg>
|
|
36
|
+
),
|
|
37
|
+
whatsapp: (
|
|
38
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden className="w-5 h-5">
|
|
39
|
+
<path d="M12 2a10 10 0 0 0-8.6 15l-1.4 5 5.2-1.4A10 10 0 1 0 12 2zm5 14.2c-.2.6-1.2 1.2-1.7 1.2-.5.1-1.1.1-1.7-.1-.4-.1-.9-.3-1.5-.6a8.4 8.4 0 0 1-3.7-3.4c-.7-1-1-1.8-1-2.5 0-.7.4-1.1.6-1.3.2-.2.4-.2.5-.2h.4c.1 0 .3 0 .4.3l.6 1.4c.1.2 0 .3 0 .4l-.3.4-.3.3c-.1.1-.2.2-.1.4.2.4.7 1.1 1.4 1.8.9.8 1.7 1.1 1.9 1.2.2.1.3.1.5-.1l.6-.7c.2-.2.3-.2.5-.1l1.4.7c.2.1.3.2.4.3.1.2.1.6 0 1z" />
|
|
40
|
+
</svg>
|
|
41
|
+
),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const FALLBACK_ICON = (
|
|
45
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" aria-hidden className="w-5 h-5">
|
|
46
|
+
<circle cx="12" cy="12" r="9" />
|
|
47
|
+
</svg>
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
export async function Footer() {
|
|
51
|
+
"use cache";
|
|
52
|
+
const year = new Date().getFullYear();
|
|
53
|
+
return (
|
|
54
|
+
<footer className="mt-16 border-t border-border bg-foreground text-background/80 text-sm">
|
|
55
|
+
<div className="max-w-7xl mx-auto px-6 sm:px-8 pt-12 pb-8">
|
|
56
|
+
<div className="grid gap-10 md:grid-cols-[1.4fr_repeat(4,1fr)]">
|
|
57
|
+
<div>
|
|
58
|
+
<div className="flex items-center gap-2.5 mb-4">
|
|
59
|
+
<span className="grid place-items-center w-8 h-8 rounded-md bg-primary text-primary-foreground text-[13px] font-bold font-mono">
|
|
60
|
+
{brand.shortName.charAt(0).toUpperCase()}
|
|
61
|
+
</span>
|
|
62
|
+
<span className="text-background text-lg font-bold -tracking-[0.025em]">
|
|
63
|
+
{brand.name}
|
|
64
|
+
</span>
|
|
65
|
+
</div>
|
|
66
|
+
<p className="leading-relaxed mb-4 max-w-sm">{brand.footer.blurb}</p>
|
|
67
|
+
<address className="not-italic space-y-1">
|
|
68
|
+
<p className="m-0">{brand.contact.address}</p>
|
|
69
|
+
<p className="m-0">
|
|
70
|
+
<a
|
|
71
|
+
href={`tel:${brand.contact.phoneTel}`}
|
|
72
|
+
className="hover:text-background transition-colors"
|
|
73
|
+
>
|
|
74
|
+
{brand.contact.phone}
|
|
75
|
+
</a>
|
|
76
|
+
</p>
|
|
77
|
+
<p className="m-0">
|
|
78
|
+
<a
|
|
79
|
+
href={`mailto:${brand.contact.email}`}
|
|
80
|
+
className="hover:text-background transition-colors"
|
|
81
|
+
>
|
|
82
|
+
{brand.contact.email}
|
|
83
|
+
</a>
|
|
84
|
+
</p>
|
|
85
|
+
<p className="m-0 text-xs">{brand.contact.hours}</p>
|
|
86
|
+
</address>
|
|
87
|
+
<div className="flex items-center gap-3 mt-5">
|
|
88
|
+
{brand.socials.map((s) => (
|
|
89
|
+
<a
|
|
90
|
+
key={s.label}
|
|
91
|
+
href={s.href}
|
|
92
|
+
aria-label={s.label}
|
|
93
|
+
target="_blank"
|
|
94
|
+
rel="noopener noreferrer"
|
|
95
|
+
className="inline-flex items-center justify-center w-9 h-9 rounded-md border border-background/20 hover:bg-primary hover:border-primary transition-colors"
|
|
96
|
+
>
|
|
97
|
+
{(s.icon && ICONS[s.icon]) ?? FALLBACK_ICON}
|
|
98
|
+
</a>
|
|
99
|
+
))}
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
{brand.footer.sitemap.map((section) => (
|
|
103
|
+
<nav key={section.title} aria-labelledby={`footer-${section.title}`}>
|
|
104
|
+
<p
|
|
105
|
+
id={`footer-${section.title}`}
|
|
106
|
+
className="text-background font-mono mb-3 text-[11px] uppercase tracking-[0.12em]"
|
|
107
|
+
>
|
|
108
|
+
{section.title}
|
|
109
|
+
</p>
|
|
110
|
+
<ul className="space-y-2 m-0 p-0 list-none">
|
|
111
|
+
{section.links.map((link) => (
|
|
112
|
+
<li key={link.label}>
|
|
113
|
+
<Link href={link.href} className="hover:text-background transition-colors">
|
|
114
|
+
{link.label}
|
|
115
|
+
</Link>
|
|
116
|
+
</li>
|
|
117
|
+
))}
|
|
118
|
+
</ul>
|
|
119
|
+
</nav>
|
|
120
|
+
))}
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<div className="mt-12 pt-6 border-t border-background/15 flex flex-col sm:flex-row items-center justify-between gap-3 text-xs">
|
|
124
|
+
<p className="m-0">© {year} {brand.name}. All rights reserved.</p>
|
|
125
|
+
{brand.footer.poweredBy && (
|
|
126
|
+
<p className="m-0 inline-flex items-center gap-1.5">
|
|
127
|
+
<span className="opacity-70">Powered by</span>
|
|
128
|
+
<a
|
|
129
|
+
href={brand.footer.poweredBy.href}
|
|
130
|
+
target="_blank"
|
|
131
|
+
rel="noopener noreferrer"
|
|
132
|
+
aria-label={brand.footer.poweredBy.label}
|
|
133
|
+
className="inline-flex items-center gap-1 text-background hover:text-primary transition-colors"
|
|
134
|
+
>
|
|
135
|
+
<span className="font-semibold tracking-tight">{brand.footer.poweredBy.label}</span>
|
|
136
|
+
<svg
|
|
137
|
+
viewBox="0 0 12 12"
|
|
138
|
+
aria-hidden
|
|
139
|
+
className="w-3 h-3 opacity-70"
|
|
140
|
+
fill="none"
|
|
141
|
+
stroke="currentColor"
|
|
142
|
+
strokeWidth="1.5"
|
|
143
|
+
>
|
|
144
|
+
<path d="M3 9L9 3M9 3H4M9 3v5" strokeLinecap="round" strokeLinejoin="round" />
|
|
145
|
+
</svg>
|
|
146
|
+
</a>
|
|
147
|
+
</p>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</footer>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { Suspense } from "react";
|
|
3
|
+
import { NavLink } from "./nav-link";
|
|
4
|
+
import { CartPill, CartPillSkeleton } from "./cart-pill";
|
|
5
|
+
import { brand } from "@/lib/brand";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Server-rendered header chrome. Brand mark + nav layout streams from the
|
|
9
|
+
* cache; the active-link styling and live cart count are dynamic islands
|
|
10
|
+
* mounted in their own Suspense boundaries so the chrome never blocks.
|
|
11
|
+
*/
|
|
12
|
+
export function Header() {
|
|
13
|
+
const initial = brand.shortName.charAt(0).toUpperCase();
|
|
14
|
+
return (
|
|
15
|
+
<header className="sticky top-0 z-30 flex items-center justify-between px-6 sm:px-8 py-3.5 border-b border-border bg-background/90 backdrop-blur-md">
|
|
16
|
+
<Link href="/" className="flex items-center gap-2.5 group">
|
|
17
|
+
<span className="grid place-items-center w-8 h-8 rounded-md bg-foreground text-background text-[13px] font-bold font-mono group-hover:bg-primary transition-colors">
|
|
18
|
+
{initial}
|
|
19
|
+
</span>
|
|
20
|
+
<span className="text-[18px] font-bold -tracking-[0.025em]">{brand.shortName}</span>
|
|
21
|
+
<span className="hidden sm:inline text-[10px] font-mono uppercase tracking-[0.16em] text-muted-foreground border border-border rounded px-1.5 py-0.5">
|
|
22
|
+
{brand.microTag}
|
|
23
|
+
</span>
|
|
24
|
+
</Link>
|
|
25
|
+
<nav className="flex items-center gap-5 sm:gap-6">
|
|
26
|
+
{brand.header.nav.map((link) => (
|
|
27
|
+
<Suspense key={link.href} fallback={<NavLinkFallback>{link.label}</NavLinkFallback>}>
|
|
28
|
+
<NavLink href={link.href}>{link.label}</NavLink>
|
|
29
|
+
</Suspense>
|
|
30
|
+
))}
|
|
31
|
+
<Suspense fallback={<CartPillSkeleton />}>
|
|
32
|
+
<CartPill />
|
|
33
|
+
</Suspense>
|
|
34
|
+
</nav>
|
|
35
|
+
</header>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function NavLinkFallback({ children }: { children: React.ReactNode }) {
|
|
40
|
+
return (
|
|
41
|
+
<span className="text-[13px] font-medium tracking-wide text-muted-foreground">
|
|
42
|
+
{children}
|
|
43
|
+
</span>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { brand } from "@/lib/brand";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Three-card editorial panel ("the pharmacist's brief") that replaces
|
|
6
|
+
* the retail-style "best sellers" rail. Curated by the pharmacist team
|
|
7
|
+
* around the time of year — harmattan, malaria season, flu shots, etc.
|
|
8
|
+
* Strings come from `brand.healthBrief`.
|
|
9
|
+
*/
|
|
10
|
+
export function HealthBrief() {
|
|
11
|
+
const { eyebrow, title, cards } = brand.healthBrief;
|
|
12
|
+
return (
|
|
13
|
+
<section className="relative bg-muted/60 border-y border-border">
|
|
14
|
+
<div className="max-w-7xl mx-auto px-6 sm:px-8 py-16 sm:py-24">
|
|
15
|
+
<div className="max-w-2xl mb-10 sm:mb-12">
|
|
16
|
+
<p className="text-[12px] font-mono uppercase tracking-[0.2em] text-muted-foreground mb-2">
|
|
17
|
+
{eyebrow}
|
|
18
|
+
</p>
|
|
19
|
+
<h2 className="text-[clamp(1.75rem,3.2vw,2.5rem)] font-semibold m-0 -tracking-[0.025em] leading-tight">
|
|
20
|
+
{title}
|
|
21
|
+
</h2>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 sm:gap-5">
|
|
25
|
+
{cards.map((c, i) => (
|
|
26
|
+
<article
|
|
27
|
+
key={c.eyebrow + c.title}
|
|
28
|
+
className="group flex flex-col bg-card border border-border rounded-2xl p-6 sm:p-7 hover:border-primary/40 hover:shadow-[0_14px_36px_rgb(0_0_0/0.06)] transition-all"
|
|
29
|
+
>
|
|
30
|
+
<div className="flex items-center justify-between mb-4">
|
|
31
|
+
<span className="inline-flex items-center gap-2 px-2.5 py-1 rounded-full bg-accent text-accent-foreground text-[11px] font-mono uppercase tracking-[0.14em]">
|
|
32
|
+
{c.eyebrow}
|
|
33
|
+
</span>
|
|
34
|
+
<span className="text-[11px] font-mono uppercase tracking-[0.16em] text-muted-foreground">
|
|
35
|
+
No. {String(i + 1).padStart(2, "0")}
|
|
36
|
+
</span>
|
|
37
|
+
</div>
|
|
38
|
+
<h3 className="text-xl sm:text-[22px] font-semibold m-0 -tracking-[0.02em] leading-snug">
|
|
39
|
+
{c.title}
|
|
40
|
+
</h3>
|
|
41
|
+
<p className="text-sm sm:text-[15px] text-muted-foreground leading-relaxed mt-3 flex-1">
|
|
42
|
+
{c.body}
|
|
43
|
+
</p>
|
|
44
|
+
<Link
|
|
45
|
+
href={c.ctaHref}
|
|
46
|
+
className="inline-flex items-center gap-2 mt-5 text-sm font-semibold text-primary group-hover:gap-3 transition-all"
|
|
47
|
+
>
|
|
48
|
+
{c.ctaLabel}
|
|
49
|
+
<ArrowIcon />
|
|
50
|
+
</Link>
|
|
51
|
+
</article>
|
|
52
|
+
))}
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</section>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function ArrowIcon() {
|
|
60
|
+
return (
|
|
61
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="w-3.5 h-3.5" aria-hidden>
|
|
62
|
+
<path d="M5 12h14M13 6l6 6-6 6" strokeLinecap="round" strokeLinejoin="round" />
|
|
63
|
+
</svg>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
interface HeroProps {
|
|
2
|
+
badge?: string;
|
|
3
|
+
title: string;
|
|
4
|
+
subtitle?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function Hero({ badge, title, subtitle }: HeroProps) {
|
|
8
|
+
return (
|
|
9
|
+
<section className="relative px-8 py-20 text-center overflow-hidden bg-gradient-to-br from-foreground via-foreground to-primary text-background">
|
|
10
|
+
<div className="absolute inset-0 opacity-[0.06] pointer-events-none [background-image:radial-gradient(circle_at_2px_2px,white_1px,transparent_0)] [background-size:32px_32px]" />
|
|
11
|
+
<div className="relative max-w-3xl mx-auto">
|
|
12
|
+
{badge && (
|
|
13
|
+
<span className="inline-block mb-5 px-3.5 py-1.5 rounded-full bg-primary/15 border border-primary/30 text-primary-foreground/90 text-[11px] font-semibold uppercase tracking-[0.16em] font-mono">
|
|
14
|
+
{badge}
|
|
15
|
+
</span>
|
|
16
|
+
)}
|
|
17
|
+
<h1 className="text-[clamp(2.5rem,6vw,4rem)] font-bold m-0 -tracking-[0.03em] leading-[1.05]">
|
|
18
|
+
{title}
|
|
19
|
+
</h1>
|
|
20
|
+
{subtitle && (
|
|
21
|
+
<p className="mx-auto mt-5 max-w-2xl text-base sm:text-lg text-background/80 leading-relaxed">
|
|
22
|
+
{subtitle}
|
|
23
|
+
</p>
|
|
24
|
+
)}
|
|
25
|
+
</div>
|
|
26
|
+
</section>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { usePathname } from "next/navigation";
|
|
5
|
+
|
|
6
|
+
export function NavLink({ href, children }: { href: string; children: React.ReactNode }) {
|
|
7
|
+
const pathname = usePathname();
|
|
8
|
+
const active = pathname === href;
|
|
9
|
+
return (
|
|
10
|
+
<Link
|
|
11
|
+
href={href}
|
|
12
|
+
className={[
|
|
13
|
+
"text-[13px] font-medium tracking-wide transition-colors",
|
|
14
|
+
active ? "text-primary" : "text-muted-foreground hover:text-foreground",
|
|
15
|
+
].join(" ")}
|
|
16
|
+
>
|
|
17
|
+
{children}
|
|
18
|
+
</Link>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { brand } from "@/lib/brand";
|
|
5
|
+
|
|
6
|
+
export function Newsletter() {
|
|
7
|
+
const n = brand.newsletter;
|
|
8
|
+
const [email, setEmail] = useState("");
|
|
9
|
+
const [submitted, setSubmitted] = useState(false);
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<section className="max-w-7xl mx-auto px-6 sm:px-8 py-14 sm:py-20">
|
|
13
|
+
<div className="rounded-3xl border border-border bg-card p-8 sm:p-12 grid grid-cols-1 lg:grid-cols-2 gap-8 items-center">
|
|
14
|
+
<div>
|
|
15
|
+
<p className="text-[11px] font-mono uppercase tracking-[0.16em] text-primary mb-2">
|
|
16
|
+
{n.eyebrow}
|
|
17
|
+
</p>
|
|
18
|
+
<h2 className="text-[clamp(1.5rem,3vw,2rem)] font-bold m-0 mb-3 -tracking-[0.025em]">
|
|
19
|
+
{n.title}
|
|
20
|
+
</h2>
|
|
21
|
+
<p className="text-muted-foreground leading-relaxed">{n.body}</p>
|
|
22
|
+
</div>
|
|
23
|
+
<form
|
|
24
|
+
onSubmit={(e) => {
|
|
25
|
+
e.preventDefault();
|
|
26
|
+
setSubmitted(true);
|
|
27
|
+
}}
|
|
28
|
+
className="flex flex-col sm:flex-row gap-2"
|
|
29
|
+
>
|
|
30
|
+
<input
|
|
31
|
+
type="email"
|
|
32
|
+
required
|
|
33
|
+
value={email}
|
|
34
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
35
|
+
placeholder={n.placeholder}
|
|
36
|
+
disabled={submitted}
|
|
37
|
+
className="flex-1 px-4 py-3 rounded-md bg-background border border-border focus:border-primary focus:ring-2 focus:ring-primary/20 outline-none transition-shadow text-sm disabled:opacity-50"
|
|
38
|
+
/>
|
|
39
|
+
<button
|
|
40
|
+
type="submit"
|
|
41
|
+
disabled={submitted}
|
|
42
|
+
className="inline-flex items-center justify-center gap-2 px-5 py-3 rounded-md bg-foreground text-background font-semibold text-sm hover:bg-primary transition-colors disabled:opacity-50"
|
|
43
|
+
>
|
|
44
|
+
{submitted ? n.successLabel : n.submitLabel}
|
|
45
|
+
</button>
|
|
46
|
+
</form>
|
|
47
|
+
</div>
|
|
48
|
+
</section>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { brand } from "@/lib/brand";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Pharmacy-specific hero. Editorial split: tagline + key trust line on
|
|
6
|
+
* the left, a stack of "open now / license / delivery" info chips on
|
|
7
|
+
* the right. Designed to sit directly above `<UrgentCtas />` which
|
|
8
|
+
* vertically overlaps the bottom edge — so this hero stays compact
|
|
9
|
+
* (no big product photo).
|
|
10
|
+
*/
|
|
11
|
+
export function PharmacyHero() {
|
|
12
|
+
return (
|
|
13
|
+
<section className="relative overflow-hidden bg-foreground text-background pb-20 sm:pb-28">
|
|
14
|
+
<div className="absolute inset-0 opacity-[0.04] pointer-events-none [background-image:radial-gradient(circle_at_2px_2px,white_1px,transparent_0)] [background-size:36px_36px]" />
|
|
15
|
+
<div className="absolute -top-32 -right-24 w-[28rem] h-[28rem] rounded-full bg-primary/20 blur-[120px] pointer-events-none" />
|
|
16
|
+
|
|
17
|
+
<div className="relative max-w-7xl mx-auto px-6 sm:px-8 pt-14 sm:pt-20">
|
|
18
|
+
<div className="grid grid-cols-1 lg:grid-cols-[1.4fr_1fr] gap-10 lg:gap-14 items-end">
|
|
19
|
+
<div>
|
|
20
|
+
<span className="inline-flex items-center gap-2 mb-6 px-3 py-1.5 rounded-full bg-background/10 text-background/90 text-[11px] font-mono uppercase tracking-[0.18em]">
|
|
21
|
+
<span className="grid place-items-center w-1.5 h-1.5 rounded-full bg-green-400 animate-pulse" />
|
|
22
|
+
{brand.hero.badge}
|
|
23
|
+
</span>
|
|
24
|
+
<h1 className="text-[clamp(2.5rem,6.4vw,5.25rem)] font-bold m-0 -tracking-[0.04em] leading-[0.98]">
|
|
25
|
+
{brand.hero.title}
|
|
26
|
+
</h1>
|
|
27
|
+
<p className="text-base sm:text-lg text-background/75 leading-relaxed max-w-xl mt-6">
|
|
28
|
+
{brand.hero.subtitle}
|
|
29
|
+
</p>
|
|
30
|
+
<div className="flex flex-wrap items-center gap-3 mt-8">
|
|
31
|
+
<Link
|
|
32
|
+
href="/shop"
|
|
33
|
+
className="inline-flex items-center gap-2 px-6 py-3 rounded-full bg-background text-foreground text-sm font-semibold hover:bg-background/90 transition-colors"
|
|
34
|
+
>
|
|
35
|
+
{brand.hero.primaryCtaLabel}
|
|
36
|
+
<ArrowIcon />
|
|
37
|
+
</Link>
|
|
38
|
+
{brand.hero.secondaryCtaLabel && brand.hero.secondaryCtaHref && (
|
|
39
|
+
<Link
|
|
40
|
+
href={brand.hero.secondaryCtaHref}
|
|
41
|
+
className="inline-flex items-center gap-2 px-6 py-3 rounded-full border border-background/30 text-background text-sm font-semibold hover:bg-background/10 transition-colors"
|
|
42
|
+
>
|
|
43
|
+
{brand.hero.secondaryCtaLabel}
|
|
44
|
+
</Link>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<aside className="grid grid-cols-2 gap-3 sm:gap-4 lg:max-w-md">
|
|
50
|
+
<InfoChip
|
|
51
|
+
eyebrow="Open now"
|
|
52
|
+
value={brand.contact.hours.split("·")[0]?.trim() ?? brand.contact.hours}
|
|
53
|
+
live
|
|
54
|
+
/>
|
|
55
|
+
<InfoChip eyebrow="Delivery" value="Free · same-day · Accra" />
|
|
56
|
+
<InfoChip eyebrow="Licensed" value="Pharmacy Council of Ghana" />
|
|
57
|
+
<InfoChip eyebrow="Insurance" value="NHIS + private accepted" />
|
|
58
|
+
</aside>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</section>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface InfoChipProps {
|
|
66
|
+
eyebrow: string;
|
|
67
|
+
value: string;
|
|
68
|
+
live?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function InfoChip({ eyebrow, value, live }: InfoChipProps) {
|
|
72
|
+
return (
|
|
73
|
+
<div className="rounded-2xl border border-background/15 bg-background/5 backdrop-blur-sm p-4">
|
|
74
|
+
<div className="flex items-center gap-2 mb-1.5">
|
|
75
|
+
<span className="text-[10px] font-mono uppercase tracking-[0.18em] text-background/55">
|
|
76
|
+
{eyebrow}
|
|
77
|
+
</span>
|
|
78
|
+
{live && (
|
|
79
|
+
<span className="grid place-items-center w-1.5 h-1.5 rounded-full bg-green-400 animate-pulse" />
|
|
80
|
+
)}
|
|
81
|
+
</div>
|
|
82
|
+
<p className="text-sm font-semibold text-background -tracking-[0.01em] leading-snug">
|
|
83
|
+
{value}
|
|
84
|
+
</p>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function ArrowIcon() {
|
|
90
|
+
return (
|
|
91
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="w-3.5 h-3.5" aria-hidden>
|
|
92
|
+
<path d="M5 12h14M13 6l6 6-6 6" strokeLinecap="round" strokeLinejoin="round" />
|
|
93
|
+
</svg>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { BrandPolicySection } from "@/lib/brand";
|
|
2
|
+
|
|
3
|
+
interface PolicyShape {
|
|
4
|
+
eyebrow: string;
|
|
5
|
+
title: string;
|
|
6
|
+
lastUpdated?: string;
|
|
7
|
+
sections: BrandPolicySection[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Shared layout for shipping / returns / accessibility / terms / privacy.
|
|
12
|
+
* Reads a `{ eyebrow, title, lastUpdated, sections[] }` block from brand.
|
|
13
|
+
*/
|
|
14
|
+
export function PolicyPage({ policy }: { policy: PolicyShape }) {
|
|
15
|
+
return (
|
|
16
|
+
<article className="max-w-3xl mx-auto px-8 py-16 prose prose-lg max-w-none">
|
|
17
|
+
<p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-primary mb-2 not-prose">
|
|
18
|
+
{policy.eyebrow}
|
|
19
|
+
</p>
|
|
20
|
+
<h1 className="text-[clamp(2.25rem,5vw,3.5rem)] font-semibold mb-2 -tracking-[0.02em]">
|
|
21
|
+
{policy.title}
|
|
22
|
+
</h1>
|
|
23
|
+
{policy.lastUpdated && (
|
|
24
|
+
<p className="text-sm text-muted-foreground not-prose mb-10">
|
|
25
|
+
Last updated: {policy.lastUpdated}
|
|
26
|
+
</p>
|
|
27
|
+
)}
|
|
28
|
+
<section className="space-y-5 leading-relaxed text-foreground/90">
|
|
29
|
+
{policy.sections.map((s) => (
|
|
30
|
+
<div key={s.heading}>
|
|
31
|
+
<h2 className="text-2xl font-semibold mt-0">{s.heading}</h2>
|
|
32
|
+
{typeof s.body === "string" ? (
|
|
33
|
+
<p>{s.body}</p>
|
|
34
|
+
) : (
|
|
35
|
+
<>
|
|
36
|
+
<p>{s.body.intro}</p>
|
|
37
|
+
<ul className="list-disc pl-6 space-y-2">
|
|
38
|
+
{s.body.bullets.map((b) => (
|
|
39
|
+
<li key={b}>{b}</li>
|
|
40
|
+
))}
|
|
41
|
+
</ul>
|
|
42
|
+
</>
|
|
43
|
+
)}
|
|
44
|
+
</div>
|
|
45
|
+
))}
|
|
46
|
+
</section>
|
|
47
|
+
</article>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { brand } from "@/lib/brand";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Full-width promo banner — reads brand.promo. Renders nothing if the
|
|
6
|
+
* brand doesn't define a promo, so this component is safe to drop in
|
|
7
|
+
* across templates that may or may not run a campaign.
|
|
8
|
+
*/
|
|
9
|
+
export function PromoBanner() {
|
|
10
|
+
const promo = brand.promo;
|
|
11
|
+
if (!promo) return null;
|
|
12
|
+
return (
|
|
13
|
+
<section className="max-w-7xl mx-auto px-6 sm:px-8 py-6">
|
|
14
|
+
<div className="relative overflow-hidden rounded-3xl bg-gradient-to-br from-primary via-primary to-foreground text-primary-foreground p-8 sm:p-12 lg:p-16">
|
|
15
|
+
<div className="absolute -top-20 -right-20 w-72 h-72 rounded-full bg-background/10 blur-3xl pointer-events-none" />
|
|
16
|
+
<div className="absolute -bottom-32 -left-20 w-96 h-96 rounded-full bg-foreground/30 blur-3xl pointer-events-none" />
|
|
17
|
+
<div className="relative grid grid-cols-1 lg:grid-cols-[1fr_auto] gap-8 items-center">
|
|
18
|
+
<div className="max-w-2xl">
|
|
19
|
+
<span className="inline-flex items-center gap-2 mb-4 px-3 py-1 rounded-full bg-background/15 border border-background/25 text-[11px] font-mono uppercase tracking-[0.16em]">
|
|
20
|
+
<span className="w-1.5 h-1.5 rounded-full bg-background animate-pulse" />
|
|
21
|
+
{promo.badge}
|
|
22
|
+
</span>
|
|
23
|
+
<h2 className="text-[clamp(1.75rem,3.5vw,2.75rem)] font-bold m-0 mb-3 -tracking-[0.025em] leading-[1.1]">
|
|
24
|
+
{promo.title}
|
|
25
|
+
</h2>
|
|
26
|
+
<p className="text-base sm:text-lg opacity-90 leading-relaxed">{promo.body}</p>
|
|
27
|
+
</div>
|
|
28
|
+
<Link
|
|
29
|
+
href={promo.ctaHref}
|
|
30
|
+
className="inline-flex items-center gap-2 px-5 py-3 rounded-md bg-background text-foreground font-semibold text-sm hover:bg-background/90 transition-colors whitespace-nowrap"
|
|
31
|
+
>
|
|
32
|
+
{promo.ctaLabel}
|
|
33
|
+
<svg viewBox="0 0 12 12" className="w-3 h-3" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden>
|
|
34
|
+
<path d="M3 6h7m0 0L7 3m3 3L7 9" strokeLinecap="round" strokeLinejoin="round" />
|
|
35
|
+
</svg>
|
|
36
|
+
</Link>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</section>
|
|
40
|
+
);
|
|
41
|
+
}
|