@cimplify/cli 0.2.8 → 0.3.1
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-OUMIT4YX.mjs} +10 -10
- package/dist/assets-DMK2QOPD.mjs +208 -0
- package/dist/chunk-42PFJBC6.mjs +5707 -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-I6P3I2YJ.mjs +259 -0
- package/dist/{chunk-UPEHLREA.mjs → chunk-IQJ45AK3.mjs} +3 -3
- package/dist/{chunk-JJYWETGA.mjs → chunk-LS2VTSMQ.mjs} +8 -2
- 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/{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-AY7VDIJZ.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-QZVAK5I3.mjs +223 -0
- package/dist/introspect-MNTC26UY.mjs +8 -0
- package/dist/{link-P4K2HRXY.mjs → link-X3E4UZBF.mjs} +4 -4
- package/dist/{list-44MLIFI2.mjs → list-TEQ73IR7.mjs} +3 -3
- package/dist/{login-RSKGT6GU.mjs → login-7O7ZXKU3.mjs} +9 -15
- 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-2DCENLHM.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/chunk-H2HJQGFY.mjs +0 -3911
- package/dist/repo-E6SBKVDG.mjs +0 -8
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo, type ReactNode } from "react";
|
|
4
|
+
import { createCimplifyClient } from "@cimplify/sdk";
|
|
5
|
+
import { CimplifyProvider, CartDrawerProvider } from "@cimplify/sdk/react";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Boots the Cimplify SDK client once on the client-side and exposes it via
|
|
9
|
+
* <CimplifyProvider/>.
|
|
10
|
+
*
|
|
11
|
+
* Base-URL resolution:
|
|
12
|
+
* 1) If NEXT_PUBLIC_CIMPLIFY_API_URL is set, use it verbatim.
|
|
13
|
+
* 2) Otherwise use the current origin so requests flow through the
|
|
14
|
+
* Next.js rewrite in `next.config.ts` to the mock at :8787 (no CORS).
|
|
15
|
+
* 3) Fall back to 127.0.0.1:8787 only during SSR, when there's no window.
|
|
16
|
+
*/
|
|
17
|
+
export function Providers({ children }: { children: ReactNode }) {
|
|
18
|
+
const client = useMemo(() => {
|
|
19
|
+
const baseUrl =
|
|
20
|
+
process.env.NEXT_PUBLIC_CIMPLIFY_API_URL?.trim() ||
|
|
21
|
+
(typeof window !== "undefined" ? window.location.origin : "http://127.0.0.1:8787");
|
|
22
|
+
const publicKey = process.env.NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY ?? "mock-dev";
|
|
23
|
+
return createCimplifyClient({
|
|
24
|
+
baseUrl,
|
|
25
|
+
publicKey,
|
|
26
|
+
suppressPublicKeyWarning: true,
|
|
27
|
+
});
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<CimplifyProvider client={client}>
|
|
32
|
+
<CartDrawerProvider>{children}</CartDrawerProvider>
|
|
33
|
+
</CimplifyProvider>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
|
|
3
|
+
interface SectionHeadingProps {
|
|
4
|
+
eyebrow: string;
|
|
5
|
+
title: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
link?: { label: string; href: string };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function SectionHeading({ eyebrow, title, description, link }: SectionHeadingProps) {
|
|
11
|
+
return (
|
|
12
|
+
<div className="flex items-end justify-between gap-6 mb-8">
|
|
13
|
+
<div className="max-w-2xl">
|
|
14
|
+
<p className="text-[11px] font-mono uppercase tracking-[0.16em] text-primary mb-2">
|
|
15
|
+
{eyebrow}
|
|
16
|
+
</p>
|
|
17
|
+
<h2 className="text-[clamp(1.75rem,3vw,2.25rem)] font-bold m-0 -tracking-[0.025em]">
|
|
18
|
+
{title}
|
|
19
|
+
</h2>
|
|
20
|
+
{description && (
|
|
21
|
+
<p className="mt-2 text-muted-foreground">{description}</p>
|
|
22
|
+
)}
|
|
23
|
+
</div>
|
|
24
|
+
{link && (
|
|
25
|
+
<Link
|
|
26
|
+
href={link.href}
|
|
27
|
+
className="text-sm font-semibold text-primary hover:underline whitespace-nowrap hidden sm:inline-flex items-center gap-1"
|
|
28
|
+
>
|
|
29
|
+
{link.label}
|
|
30
|
+
<svg viewBox="0 0 12 12" className="w-3 h-3" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden>
|
|
31
|
+
<path d="M3 6h7m0 0L7 3m3 3L7 9" strokeLinecap="round" strokeLinejoin="round" />
|
|
32
|
+
</svg>
|
|
33
|
+
</Link>
|
|
34
|
+
)}
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import {
|
|
5
|
+
CardVariant,
|
|
6
|
+
FoodProductCard,
|
|
7
|
+
RetailProductCard,
|
|
8
|
+
WholesaleProductCard,
|
|
9
|
+
DigitalProductCard,
|
|
10
|
+
BundleProductCard,
|
|
11
|
+
CompositeProductCard,
|
|
12
|
+
StandardServiceCard,
|
|
13
|
+
CompactServiceCard,
|
|
14
|
+
ScheduleServiceCard,
|
|
15
|
+
RentalServiceCard,
|
|
16
|
+
AccommodationCard,
|
|
17
|
+
LeaseServiceCard,
|
|
18
|
+
SubscriptionCard,
|
|
19
|
+
} from "@cimplify/sdk/react";
|
|
20
|
+
import type { CardLayoutProps } from "@cimplify/sdk/react";
|
|
21
|
+
import type { Product } from "@cimplify/sdk";
|
|
22
|
+
import { PRODUCT_TYPE, RENDER_HINT, DURATION_UNIT } from "@cimplify/sdk";
|
|
23
|
+
|
|
24
|
+
const RENTAL_UNITS = new Set<string>([DURATION_UNIT.Days, DURATION_UNIT.Hours]);
|
|
25
|
+
const LEASE_UNITS = new Set<string>([DURATION_UNIT.Weeks, DURATION_UNIT.Months, DURATION_UNIT.Years]);
|
|
26
|
+
|
|
27
|
+
const VARIANT_CARDS: Record<CardVariant, React.ComponentType<CardLayoutProps>> = {
|
|
28
|
+
[CardVariant.Food]: FoodProductCard,
|
|
29
|
+
[CardVariant.Retail]: RetailProductCard,
|
|
30
|
+
[CardVariant.Wholesale]: WholesaleProductCard,
|
|
31
|
+
[CardVariant.Digital]: DigitalProductCard,
|
|
32
|
+
[CardVariant.Bundle]: BundleProductCard,
|
|
33
|
+
[CardVariant.Composite]: CompositeProductCard,
|
|
34
|
+
[CardVariant.Standard]: StandardServiceCard,
|
|
35
|
+
[CardVariant.Compact]: CompactServiceCard,
|
|
36
|
+
[CardVariant.Schedule]: ScheduleServiceCard,
|
|
37
|
+
[CardVariant.Rental]: RentalServiceCard,
|
|
38
|
+
[CardVariant.Accommodation]: AccommodationCard,
|
|
39
|
+
[CardVariant.Lease]: LeaseServiceCard,
|
|
40
|
+
[CardVariant.Subscription]: SubscriptionCard,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function resolveVariant(p: Product): CardVariant {
|
|
44
|
+
if (p.type === PRODUCT_TYPE.Bundle) return CardVariant.Bundle;
|
|
45
|
+
if (p.type === PRODUCT_TYPE.Composite) return CardVariant.Composite;
|
|
46
|
+
if (p.quantity_pricing && p.quantity_pricing.length > 1) return CardVariant.Wholesale;
|
|
47
|
+
if (p.type === PRODUCT_TYPE.Digital) return CardVariant.Digital;
|
|
48
|
+
if (p.type === PRODUCT_TYPE.Service) {
|
|
49
|
+
if (p.duration_unit && RENTAL_UNITS.has(p.duration_unit)) return CardVariant.Rental;
|
|
50
|
+
if (p.duration_unit === DURATION_UNIT.Nights) return CardVariant.Accommodation;
|
|
51
|
+
if (p.duration_unit && LEASE_UNITS.has(p.duration_unit)) return CardVariant.Lease;
|
|
52
|
+
if (p.billing_plans && p.billing_plans.length > 0 && !p.duration_minutes) return CardVariant.Subscription;
|
|
53
|
+
return CardVariant.Standard;
|
|
54
|
+
}
|
|
55
|
+
if (p.render_hint === RENDER_HINT.Food) return CardVariant.Food;
|
|
56
|
+
return CardVariant.Retail;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface Props {
|
|
60
|
+
product: Product;
|
|
61
|
+
/** Override the auto-detected card variant. */
|
|
62
|
+
variant?: CardVariant;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Variant-aware product card that links to the dedicated product page at
|
|
67
|
+
* `/products/<slug>`. Statically pre-renderable (no `useSearchParams`),
|
|
68
|
+
* with `prefetch` enabled so hover → instant nav. Full product page
|
|
69
|
+
* (vs a modal) is the right pattern for pharmacy: dosage info, prescription
|
|
70
|
+
* upload inputs, consent signatures, and pharmacist notes all need vertical
|
|
71
|
+
* real estate.
|
|
72
|
+
*/
|
|
73
|
+
export function StoreProductCard({ product, variant }: Props) {
|
|
74
|
+
const slug = product.slug || product.id;
|
|
75
|
+
const Card = VARIANT_CARDS[variant ?? resolveVariant(product)];
|
|
76
|
+
const href = `/products/${encodeURIComponent(slug)}`;
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<Card
|
|
80
|
+
product={product}
|
|
81
|
+
renderLink={({ className, children }) => (
|
|
82
|
+
<Link href={href} className={className}>
|
|
83
|
+
{children}
|
|
84
|
+
</Link>
|
|
85
|
+
)}
|
|
86
|
+
/>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { brand } from "@/lib/brand";
|
|
3
|
+
|
|
4
|
+
type IconKey = (typeof brand.symptomFinder.tiles)[number]["iconKey"];
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Symptom-led entry grid — the pharmacy answer to the retail
|
|
8
|
+
* "category tiles" row. Eight tiles ("Pain & fever", "Cold & flu",
|
|
9
|
+
* "Baby care", …) each linked to the appropriate category page, with
|
|
10
|
+
* an inline SVG icon keyed by `iconKey`. Strings + ordering come from
|
|
11
|
+
* `brand.symptomFinder`.
|
|
12
|
+
*/
|
|
13
|
+
export function SymptomFinder() {
|
|
14
|
+
const { eyebrow, title, description, tiles } = brand.symptomFinder;
|
|
15
|
+
return (
|
|
16
|
+
<section className="max-w-7xl mx-auto px-6 sm:px-8 py-14 sm:py-20">
|
|
17
|
+
<div className="max-w-2xl mb-8 sm:mb-10">
|
|
18
|
+
<p className="text-[12px] font-mono uppercase tracking-[0.2em] text-muted-foreground mb-2">
|
|
19
|
+
{eyebrow}
|
|
20
|
+
</p>
|
|
21
|
+
<h2 className="text-[clamp(1.75rem,3.2vw,2.5rem)] font-semibold m-0 -tracking-[0.025em] leading-tight">
|
|
22
|
+
{title}
|
|
23
|
+
</h2>
|
|
24
|
+
<p className="text-sm sm:text-base text-muted-foreground mt-3 leading-relaxed">
|
|
25
|
+
{description}
|
|
26
|
+
</p>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4">
|
|
30
|
+
{tiles.map((t) => (
|
|
31
|
+
<Link
|
|
32
|
+
key={t.label + t.iconKey}
|
|
33
|
+
href={t.href}
|
|
34
|
+
className="group relative flex flex-col items-start gap-3 p-5 sm:p-6 rounded-2xl bg-card border border-border hover:border-primary/40 hover:shadow-[0_10px_30px_rgb(0_0_0/0.06)] transition-all"
|
|
35
|
+
>
|
|
36
|
+
<span className="grid place-items-center w-11 h-11 rounded-xl bg-accent text-accent-foreground group-hover:bg-primary group-hover:text-primary-foreground transition-colors">
|
|
37
|
+
{ICONS[t.iconKey]}
|
|
38
|
+
</span>
|
|
39
|
+
<span className="text-[15px] sm:text-base font-semibold tracking-tight">
|
|
40
|
+
{t.label}
|
|
41
|
+
</span>
|
|
42
|
+
<span className="absolute top-5 right-5 text-muted-foreground/40 group-hover:text-primary group-hover:translate-x-0.5 transition-all">
|
|
43
|
+
<ArrowIcon />
|
|
44
|
+
</span>
|
|
45
|
+
</Link>
|
|
46
|
+
))}
|
|
47
|
+
</div>
|
|
48
|
+
</section>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const ICONS: Record<IconKey, React.ReactNode> = {
|
|
53
|
+
pain: (
|
|
54
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" className="w-6 h-6" aria-hidden>
|
|
55
|
+
<path d="M12 3a4 4 0 0 0-4 4v2H6a3 3 0 0 0 0 6h2v2a4 4 0 0 0 8 0v-2h2a3 3 0 0 0 0-6h-2V7a4 4 0 0 0-4-4z" strokeLinejoin="round" />
|
|
56
|
+
</svg>
|
|
57
|
+
),
|
|
58
|
+
cold: (
|
|
59
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" className="w-6 h-6" aria-hidden>
|
|
60
|
+
<path d="M12 3v18M3 12h18M5 5l14 14M19 5L5 19" strokeLinecap="round" />
|
|
61
|
+
</svg>
|
|
62
|
+
),
|
|
63
|
+
allergy: (
|
|
64
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" className="w-6 h-6" aria-hidden>
|
|
65
|
+
<circle cx="12" cy="12" r="3" />
|
|
66
|
+
<path d="M12 4v3M12 17v3M4 12h3M17 12h3M6 6l2 2M16 16l2 2M6 18l2-2M16 8l2-2" strokeLinecap="round" />
|
|
67
|
+
</svg>
|
|
68
|
+
),
|
|
69
|
+
sleep: (
|
|
70
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" className="w-6 h-6" aria-hidden>
|
|
71
|
+
<path d="M21 13A9 9 0 1 1 11 3a7 7 0 0 0 10 10z" strokeLinejoin="round" />
|
|
72
|
+
</svg>
|
|
73
|
+
),
|
|
74
|
+
baby: (
|
|
75
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" className="w-6 h-6" aria-hidden>
|
|
76
|
+
<circle cx="12" cy="9" r="4" />
|
|
77
|
+
<path d="M9 9c0 .5.5 1 1 1M14 9c0 .5.5 1 1 1" strokeLinecap="round" />
|
|
78
|
+
<path d="M5 20c1-3 4-5 7-5s6 2 7 5" strokeLinecap="round" strokeLinejoin="round" />
|
|
79
|
+
</svg>
|
|
80
|
+
),
|
|
81
|
+
"first-aid": (
|
|
82
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" className="w-6 h-6" aria-hidden>
|
|
83
|
+
<rect x="3" y="7" width="18" height="13" rx="2" />
|
|
84
|
+
<path d="M9 7V5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2" />
|
|
85
|
+
<path d="M12 11v6M9 14h6" strokeLinecap="round" />
|
|
86
|
+
</svg>
|
|
87
|
+
),
|
|
88
|
+
vitamins: (
|
|
89
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" className="w-6 h-6" aria-hidden>
|
|
90
|
+
<rect x="3.5" y="9" width="17" height="6" rx="3" transform="rotate(-30 12 12)" />
|
|
91
|
+
<path d="M9 9.5l5.5 5.5" />
|
|
92
|
+
</svg>
|
|
93
|
+
),
|
|
94
|
+
diabetes: (
|
|
95
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" className="w-6 h-6" aria-hidden>
|
|
96
|
+
<path d="M12 3l5 5a7 7 0 1 1-10 0l5-5z" strokeLinejoin="round" />
|
|
97
|
+
<path d="M9 14h6M12 11v6" strokeLinecap="round" />
|
|
98
|
+
</svg>
|
|
99
|
+
),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
function ArrowIcon() {
|
|
103
|
+
return (
|
|
104
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="w-4 h-4" aria-hidden>
|
|
105
|
+
<path d="M5 12h14M13 6l6 6-6 6" strokeLinecap="round" strokeLinejoin="round" />
|
|
106
|
+
</svg>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { brand } from "@/lib/brand";
|
|
3
|
+
|
|
4
|
+
export function TradeInCta() {
|
|
5
|
+
const t = brand.tradeIn;
|
|
6
|
+
if (!t) return null;
|
|
7
|
+
return (
|
|
8
|
+
<section className="max-w-7xl mx-auto px-6 sm:px-8 py-14 sm:py-20">
|
|
9
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 items-center bg-foreground text-background rounded-3xl p-8 sm:p-12 lg:p-14 overflow-hidden relative">
|
|
10
|
+
<div className="absolute top-0 right-0 w-1/2 h-full opacity-[0.04] [background-image:linear-gradient(135deg,white_1px,transparent_1px)] [background-size:24px_24px] pointer-events-none" />
|
|
11
|
+
<div>
|
|
12
|
+
<p className="text-[11px] font-mono uppercase tracking-[0.16em] text-primary-foreground/70 mb-3">
|
|
13
|
+
{t.eyebrow}
|
|
14
|
+
</p>
|
|
15
|
+
<h2 className="text-[clamp(1.75rem,3.5vw,2.5rem)] font-bold m-0 mb-4 -tracking-[0.025em] leading-[1.1]">
|
|
16
|
+
{t.title}
|
|
17
|
+
</h2>
|
|
18
|
+
<p className="text-background/75 leading-relaxed mb-6">{t.body}</p>
|
|
19
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
20
|
+
<Link
|
|
21
|
+
href={t.primaryCtaHref}
|
|
22
|
+
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"
|
|
23
|
+
>
|
|
24
|
+
{t.primaryCtaLabel}
|
|
25
|
+
<svg viewBox="0 0 12 12" className="w-3 h-3" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden>
|
|
26
|
+
<path d="M3 6h7m0 0L7 3m3 3L7 9" strokeLinecap="round" strokeLinejoin="round" />
|
|
27
|
+
</svg>
|
|
28
|
+
</Link>
|
|
29
|
+
<Link
|
|
30
|
+
href={t.secondaryCtaHref}
|
|
31
|
+
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-md border border-background/25 hover:bg-background/10 transition-colors text-sm font-medium"
|
|
32
|
+
>
|
|
33
|
+
{t.secondaryCtaLabel}
|
|
34
|
+
</Link>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
<div className="grid grid-cols-3 gap-3">
|
|
38
|
+
{t.steps.map((s) => (
|
|
39
|
+
<div
|
|
40
|
+
key={s.step}
|
|
41
|
+
className="rounded-2xl p-4 sm:p-5 bg-background/5 border border-background/10"
|
|
42
|
+
>
|
|
43
|
+
<p className="text-[10px] font-mono text-primary tabular-nums mb-3">{s.step}</p>
|
|
44
|
+
<p className="text-[13px] sm:text-sm font-semibold mb-1.5 -tracking-[0.015em]">
|
|
45
|
+
{s.title}
|
|
46
|
+
</p>
|
|
47
|
+
<p className="text-[11px] sm:text-xs text-background/65 leading-snug">{s.body}</p>
|
|
48
|
+
</div>
|
|
49
|
+
))}
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</section>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { brand } from "@/lib/brand";
|
|
2
|
+
|
|
3
|
+
const ICONS: Record<string, React.ReactNode> = {
|
|
4
|
+
delivery: (
|
|
5
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-5 h-5" aria-hidden>
|
|
6
|
+
<path d="M3 7h12v8H3zM15 10h4l3 3v2h-7z" strokeLinejoin="round" />
|
|
7
|
+
<circle cx="7" cy="17" r="1.5" />
|
|
8
|
+
<circle cx="18" cy="17" r="1.5" />
|
|
9
|
+
</svg>
|
|
10
|
+
),
|
|
11
|
+
warranty: (
|
|
12
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-5 h-5" aria-hidden>
|
|
13
|
+
<path d="M12 3l8 3v6c0 5-3.5 8-8 9-4.5-1-8-4-8-9V6l8-3z" strokeLinejoin="round" />
|
|
14
|
+
<path d="M9 12l2 2 4-4" strokeLinecap="round" strokeLinejoin="round" />
|
|
15
|
+
</svg>
|
|
16
|
+
),
|
|
17
|
+
payment: (
|
|
18
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-5 h-5" aria-hidden>
|
|
19
|
+
<rect x="3" y="6" width="18" height="12" rx="2" />
|
|
20
|
+
<line x1="3" y1="10" x2="21" y2="10" />
|
|
21
|
+
</svg>
|
|
22
|
+
),
|
|
23
|
+
verified: (
|
|
24
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-5 h-5" aria-hidden>
|
|
25
|
+
<circle cx="12" cy="12" r="9" />
|
|
26
|
+
<path d="M8 12l3 3 5-6" strokeLinecap="round" strokeLinejoin="round" />
|
|
27
|
+
</svg>
|
|
28
|
+
),
|
|
29
|
+
support: (
|
|
30
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-5 h-5" aria-hidden>
|
|
31
|
+
<path d="M18 18a8 8 0 1 0-12 0" />
|
|
32
|
+
<path d="M3 18h4v3H3zM17 18h4v3h-4z" strokeLinejoin="round" />
|
|
33
|
+
</svg>
|
|
34
|
+
),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const FALLBACK_ICON = (
|
|
38
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-5 h-5" aria-hidden>
|
|
39
|
+
<circle cx="12" cy="12" r="9" />
|
|
40
|
+
</svg>
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
export function TrustBar() {
|
|
44
|
+
const items = brand.trustItems;
|
|
45
|
+
if (!items || items.length === 0) return null;
|
|
46
|
+
return (
|
|
47
|
+
<section className="border-y border-border bg-card">
|
|
48
|
+
<div className="max-w-7xl mx-auto px-6 sm:px-8 py-10 grid grid-cols-2 md:grid-cols-4 gap-x-6 gap-y-8">
|
|
49
|
+
{items.map((it) => (
|
|
50
|
+
<div key={it.label} className="flex items-start gap-3">
|
|
51
|
+
<div className="grid place-items-center w-10 h-10 rounded-lg bg-primary/10 text-primary shrink-0">
|
|
52
|
+
{ICONS[it.iconKey] ?? FALLBACK_ICON}
|
|
53
|
+
</div>
|
|
54
|
+
<div className="min-w-0">
|
|
55
|
+
<p className="text-[10px] font-mono uppercase tracking-[0.16em] text-muted-foreground mb-0.5">
|
|
56
|
+
{it.label}
|
|
57
|
+
</p>
|
|
58
|
+
<p className="text-base font-semibold -tracking-[0.015em] mb-0.5">{it.value}</p>
|
|
59
|
+
<p className="text-xs text-muted-foreground leading-snug">{it.description}</p>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
))}
|
|
63
|
+
</div>
|
|
64
|
+
</section>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { brand } from "@/lib/brand";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Two-column urgent-action panel for the pharmacy home page. Left card is
|
|
6
|
+
* prescription upload; right card is pharmacist consult. Sits directly
|
|
7
|
+
* under the hero — the two things a pharmacy visitor is most likely to
|
|
8
|
+
* want, surfaced before any browsing.
|
|
9
|
+
*/
|
|
10
|
+
export function UrgentCtas() {
|
|
11
|
+
const { prescription, consult } = brand.urgentCtas;
|
|
12
|
+
return (
|
|
13
|
+
<section className="max-w-7xl mx-auto px-6 sm:px-8 -mt-10 sm:-mt-14 relative z-10">
|
|
14
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
15
|
+
<Link
|
|
16
|
+
href={prescription.ctaHref}
|
|
17
|
+
className="group block bg-card border border-border rounded-2xl p-7 sm:p-8 shadow-[0_8px_30px_rgb(0_0_0/0.06)] hover:shadow-[0_20px_50px_rgb(0_0_0/0.10)] hover:border-primary/40 transition-all"
|
|
18
|
+
>
|
|
19
|
+
<div className="flex items-start justify-between gap-3 mb-4">
|
|
20
|
+
<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]">
|
|
21
|
+
{prescription.eyebrow}
|
|
22
|
+
</span>
|
|
23
|
+
<span className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-muted text-muted-foreground text-[10px] font-mono uppercase tracking-[0.14em]">
|
|
24
|
+
<LockIcon /> {prescription.badge}
|
|
25
|
+
</span>
|
|
26
|
+
</div>
|
|
27
|
+
<UploadIllustration />
|
|
28
|
+
<h3 className="text-[22px] sm:text-2xl font-semibold mt-5 -tracking-[0.02em] leading-snug">
|
|
29
|
+
{prescription.title}
|
|
30
|
+
</h3>
|
|
31
|
+
<p className="text-sm sm:text-[15px] text-muted-foreground leading-relaxed mt-2">
|
|
32
|
+
{prescription.body}
|
|
33
|
+
</p>
|
|
34
|
+
<span className="inline-flex items-center gap-2 mt-5 text-sm font-semibold text-primary group-hover:gap-3 transition-all">
|
|
35
|
+
{prescription.ctaLabel}
|
|
36
|
+
<ArrowIcon />
|
|
37
|
+
</span>
|
|
38
|
+
</Link>
|
|
39
|
+
|
|
40
|
+
<Link
|
|
41
|
+
href={consult.ctaHref}
|
|
42
|
+
className="group block bg-foreground text-background rounded-2xl p-7 sm:p-8 shadow-[0_8px_30px_rgb(0_0_0/0.10)] hover:shadow-[0_20px_50px_rgb(0_0_0/0.18)] transition-all"
|
|
43
|
+
>
|
|
44
|
+
<div className="flex items-start justify-between gap-3 mb-4">
|
|
45
|
+
<span className="inline-flex items-center gap-2 px-2.5 py-1 rounded-full bg-background/10 text-background text-[11px] font-mono uppercase tracking-[0.14em]">
|
|
46
|
+
{consult.eyebrow}
|
|
47
|
+
</span>
|
|
48
|
+
<span className="inline-flex items-center gap-2 px-2 py-0.5 rounded-full bg-background/10 text-background/85 text-[10px] font-mono uppercase tracking-[0.14em]">
|
|
49
|
+
<span className="grid place-items-center w-1.5 h-1.5 rounded-full bg-green-400 animate-pulse" />
|
|
50
|
+
{consult.hoursLabel}
|
|
51
|
+
</span>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div className="flex items-center gap-3 mt-2">
|
|
55
|
+
<div className="grid place-items-center w-11 h-11 rounded-full bg-background/15 text-background text-base font-semibold">
|
|
56
|
+
{consult.pharmacistName
|
|
57
|
+
.split(" ")
|
|
58
|
+
.map((s) => s[0])
|
|
59
|
+
.join("")
|
|
60
|
+
.slice(0, 2)
|
|
61
|
+
.toUpperCase()}
|
|
62
|
+
</div>
|
|
63
|
+
<div>
|
|
64
|
+
<p className="text-sm font-semibold text-background">{consult.pharmacistName}</p>
|
|
65
|
+
<p className="text-[12px] font-mono uppercase tracking-[0.14em] text-background/60">
|
|
66
|
+
{consult.hoursValue}
|
|
67
|
+
</p>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<h3 className="text-[22px] sm:text-2xl font-semibold mt-5 -tracking-[0.02em] leading-snug">
|
|
72
|
+
{consult.title}
|
|
73
|
+
</h3>
|
|
74
|
+
<p className="text-sm sm:text-[15px] text-background/75 leading-relaxed mt-2">
|
|
75
|
+
{consult.body}
|
|
76
|
+
</p>
|
|
77
|
+
<span className="inline-flex items-center gap-2 mt-5 text-sm font-semibold text-background group-hover:gap-3 transition-all">
|
|
78
|
+
{consult.ctaLabel}
|
|
79
|
+
<ArrowIcon />
|
|
80
|
+
</span>
|
|
81
|
+
</Link>
|
|
82
|
+
</div>
|
|
83
|
+
</section>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function UploadIllustration() {
|
|
88
|
+
return (
|
|
89
|
+
<div className="relative h-24 sm:h-28 rounded-xl border border-dashed border-primary/40 bg-primary/[0.04] grid place-items-center overflow-hidden">
|
|
90
|
+
<div className="flex flex-col items-center gap-1.5 text-primary/80">
|
|
91
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-7 h-7" aria-hidden>
|
|
92
|
+
<path d="M12 3v12" strokeLinecap="round" />
|
|
93
|
+
<path d="M7 8l5-5 5 5" strokeLinecap="round" strokeLinejoin="round" />
|
|
94
|
+
<path d="M5 17v3a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-3" strokeLinecap="round" strokeLinejoin="round" />
|
|
95
|
+
</svg>
|
|
96
|
+
<span className="text-[11px] font-mono uppercase tracking-[0.16em]">Drag · JPG · PDF</span>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function LockIcon() {
|
|
103
|
+
return (
|
|
104
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-3 h-3" aria-hidden>
|
|
105
|
+
<rect x="5" y="11" width="14" height="9" rx="2" />
|
|
106
|
+
<path d="M8 11V8a4 4 0 0 1 8 0v3" />
|
|
107
|
+
</svg>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function ArrowIcon() {
|
|
112
|
+
return (
|
|
113
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="w-3.5 h-3.5" aria-hidden>
|
|
114
|
+
<path d="M5 12h14M13 6l6 6-6 6" strokeLinecap="round" strokeLinejoin="round" />
|
|
115
|
+
</svg>
|
|
116
|
+
);
|
|
117
|
+
}
|