@cartbase/storefront 0.1.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.
Files changed (176) hide show
  1. package/package.json +226 -0
  2. package/src/api/auth.ts +108 -0
  3. package/src/api/carts.ts +506 -0
  4. package/src/api/categories.ts +184 -0
  5. package/src/api/checkout.ts +440 -0
  6. package/src/api/collections.ts +130 -0
  7. package/src/api/consent.ts +75 -0
  8. package/src/api/content.ts +125 -0
  9. package/src/api/customers.ts +307 -0
  10. package/src/api/gift-cards.ts +112 -0
  11. package/src/api/http.ts +122 -0
  12. package/src/api/index.ts +29 -0
  13. package/src/api/integrations.ts +130 -0
  14. package/src/api/menus.ts +77 -0
  15. package/src/api/metaobjects.ts +136 -0
  16. package/src/api/orders.ts +290 -0
  17. package/src/api/products.ts +303 -0
  18. package/src/api/redirects.ts +37 -0
  19. package/src/api/regions.ts +141 -0
  20. package/src/api/reviews.ts +259 -0
  21. package/src/api/search.ts +133 -0
  22. package/src/api/types.ts +91 -0
  23. package/src/cart-drawer/cart-drawer.tsx +86 -0
  24. package/src/cart-drawer/context.tsx +569 -0
  25. package/src/cart-drawer/continue-shopping.tsx +28 -0
  26. package/src/cart-drawer/cross-sell-carousel.tsx +212 -0
  27. package/src/cart-drawer/cross-sell-sidebar.tsx +158 -0
  28. package/src/cart-drawer/empty.tsx +52 -0
  29. package/src/cart-drawer/free-gift.tsx +71 -0
  30. package/src/cart-drawer/gift-wrap.tsx +83 -0
  31. package/src/cart-drawer/header.tsx +52 -0
  32. package/src/cart-drawer/index.ts +69 -0
  33. package/src/cart-drawer/item/index.tsx +164 -0
  34. package/src/cart-drawer/item/quantity.tsx +100 -0
  35. package/src/cart-drawer/item/upsell.tsx +110 -0
  36. package/src/cart-drawer/item/variant.tsx +46 -0
  37. package/src/cart-drawer/labels-bg.ts +72 -0
  38. package/src/cart-drawer/labels.ts +119 -0
  39. package/src/cart-drawer/notes.tsx +131 -0
  40. package/src/cart-drawer/payment-badges.tsx +96 -0
  41. package/src/cart-drawer/promo-banner.tsx +43 -0
  42. package/src/cart-drawer/rewards-points.tsx +78 -0
  43. package/src/cart-drawer/sticky-footer.tsx +73 -0
  44. package/src/cart-drawer/summary-breakdown.tsx +196 -0
  45. package/src/cart-drawer/template.tsx +225 -0
  46. package/src/cart-drawer/tiered-progress.tsx +168 -0
  47. package/src/checkout/address-error-copy.ts +119 -0
  48. package/src/checkout/address-form.tsx +224 -0
  49. package/src/checkout/address-select.tsx +79 -0
  50. package/src/checkout/boxnow-locker-selector.tsx +410 -0
  51. package/src/checkout/checkout-client.tsx +222 -0
  52. package/src/checkout/company-details.tsx +94 -0
  53. package/src/checkout/compare-addresses.ts +40 -0
  54. package/src/checkout/context.tsx +76 -0
  55. package/src/checkout/discount-section.tsx +218 -0
  56. package/src/checkout/econt-office-selector.tsx +332 -0
  57. package/src/checkout/error-message.tsx +25 -0
  58. package/src/checkout/geocode.ts +154 -0
  59. package/src/checkout/gift-card-section.tsx +224 -0
  60. package/src/checkout/index.ts +74 -0
  61. package/src/checkout/labels-bg.ts +128 -0
  62. package/src/checkout/labels.ts +263 -0
  63. package/src/checkout/line-item-card.tsx +152 -0
  64. package/src/checkout/order-summary.tsx +524 -0
  65. package/src/checkout/payment-button.tsx +373 -0
  66. package/src/checkout/payment-error-copy.ts +269 -0
  67. package/src/checkout/payment-method-list.tsx +365 -0
  68. package/src/checkout/payment-wrapper.tsx +102 -0
  69. package/src/checkout/promotion-error-copy.ts +124 -0
  70. package/src/checkout/shipping-method-list.tsx +335 -0
  71. package/src/checkout/stripe-wrapper.tsx +165 -0
  72. package/src/checkout/use-checkout-orchestration.ts +1504 -0
  73. package/src/common/cart-button-client.tsx +39 -0
  74. package/src/common/cart-button.tsx +28 -0
  75. package/src/common/country-select.tsx +65 -0
  76. package/src/common/delete-button.tsx +66 -0
  77. package/src/common/index.ts +17 -0
  78. package/src/common/language-select.tsx +78 -0
  79. package/src/common/localized-link.tsx +45 -0
  80. package/src/common/skeleton.tsx +29 -0
  81. package/src/index.ts +12 -0
  82. package/src/lib/cart-helpers.ts +113 -0
  83. package/src/lib/dual-price.tsx +73 -0
  84. package/src/lib/get-percentage-diff.ts +5 -0
  85. package/src/lib/get-product-price.ts +133 -0
  86. package/src/lib/hooks/use-intersection.ts +30 -0
  87. package/src/lib/hooks/use-toggle-state.ts +25 -0
  88. package/src/lib/money.ts +73 -0
  89. package/src/lib/payment-constants.ts +66 -0
  90. package/src/lib/product.ts +22 -0
  91. package/src/lib/sort-products.ts +63 -0
  92. package/src/lib/store-api-error.ts +36 -0
  93. package/src/lib/utils.ts +16 -0
  94. package/src/order/context.tsx +32 -0
  95. package/src/order/index.ts +63 -0
  96. package/src/order/labels-bg.ts +39 -0
  97. package/src/order/labels.ts +79 -0
  98. package/src/order/order-address-card.tsx +47 -0
  99. package/src/order/order-completed-template.tsx +165 -0
  100. package/src/order/order-confirmation-header.tsx +65 -0
  101. package/src/order/order-delivery-card.tsx +258 -0
  102. package/src/order/order-help-section.tsx +47 -0
  103. package/src/order/order-item.tsx +201 -0
  104. package/src/order/order-items-list.tsx +52 -0
  105. package/src/order/order-payment-card.tsx +95 -0
  106. package/src/order/order-timeline.tsx +141 -0
  107. package/src/order/order-totals.tsx +245 -0
  108. package/src/primitives/field.tsx +125 -0
  109. package/src/primitives/select-field.tsx +77 -0
  110. package/src/primitives/ui/accordion.tsx +61 -0
  111. package/src/primitives/ui/button.tsx +68 -0
  112. package/src/primitives/ui/collapsible.tsx +16 -0
  113. package/src/primitives/ui/dialog.tsx +112 -0
  114. package/src/primitives/ui/input.tsx +30 -0
  115. package/src/primitives/ui/label.tsx +31 -0
  116. package/src/primitives/ui/popover.tsx +38 -0
  117. package/src/primitives/ui/select.tsx +163 -0
  118. package/src/primitives/ui/sheet.tsx +131 -0
  119. package/src/primitives/ui/tabs.tsx +62 -0
  120. package/src/products/context.tsx +34 -0
  121. package/src/products/image-gallery.tsx +43 -0
  122. package/src/products/index.ts +44 -0
  123. package/src/products/labels-bg.ts +35 -0
  124. package/src/products/labels.ts +57 -0
  125. package/src/products/mobile-actions.tsx +180 -0
  126. package/src/products/option-select.tsx +67 -0
  127. package/src/products/preview-price.tsx +36 -0
  128. package/src/products/product-actions-wrapper.tsx +58 -0
  129. package/src/products/product-actions.tsx +217 -0
  130. package/src/products/product-info.tsx +43 -0
  131. package/src/products/product-preview.tsx +49 -0
  132. package/src/products/product-price.tsx +69 -0
  133. package/src/products/product-tabs.tsx +169 -0
  134. package/src/products/product-template.tsx +114 -0
  135. package/src/products/purchase-options.tsx +130 -0
  136. package/src/products/related-products.tsx +86 -0
  137. package/src/products/thumbnail.tsx +71 -0
  138. package/src/products/variant-matching.ts +71 -0
  139. package/src/reviews-ui/helpers.ts +174 -0
  140. package/src/reviews-ui/index.ts +74 -0
  141. package/src/reviews-ui/labels-bg.ts +91 -0
  142. package/src/reviews-ui/labels.ts +199 -0
  143. package/src/reviews-ui/photo-upload.tsx +345 -0
  144. package/src/reviews-ui/review-list.tsx +249 -0
  145. package/src/reviews-ui/review-widget.tsx +224 -0
  146. package/src/reviews-ui/review-wizard.tsx +560 -0
  147. package/src/reviews-ui/star-badge.tsx +104 -0
  148. package/src/reviews-ui/wizard-state.ts +81 -0
  149. package/src/store/category-template.tsx +129 -0
  150. package/src/store/collection-template.tsx +139 -0
  151. package/src/store/index.ts +41 -0
  152. package/src/store/labels-bg.ts +22 -0
  153. package/src/store/labels.ts +52 -0
  154. package/src/store/paginated-products.tsx +116 -0
  155. package/src/store/pagination.tsx +103 -0
  156. package/src/store/search-params.ts +256 -0
  157. package/src/store/search-template.tsx +249 -0
  158. package/src/store/skeleton-product-grid.tsx +26 -0
  159. package/src/store/sort-select.tsx +81 -0
  160. package/src/store/store-template.tsx +65 -0
  161. package/src/tracking/attribution.ts +418 -0
  162. package/src/tracking/consent-banner.tsx +355 -0
  163. package/src/tracking/consent-init.tsx +44 -0
  164. package/src/tracking/consent.ts +243 -0
  165. package/src/tracking/fbq.ts +168 -0
  166. package/src/tracking/ga4.tsx +49 -0
  167. package/src/tracking/get-tracking-attribution.ts +224 -0
  168. package/src/tracking/get-tracking-config.ts +50 -0
  169. package/src/tracking/gtag.ts +200 -0
  170. package/src/tracking/index.ts +133 -0
  171. package/src/tracking/meta-pixel.tsx +166 -0
  172. package/src/tracking/rybbit-events.ts +242 -0
  173. package/src/tracking/rybbit.tsx +40 -0
  174. package/src/tracking/types.ts +185 -0
  175. package/src/tracking/use-engagement-time.ts +58 -0
  176. package/tailwind-preset.cjs +72 -0
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Shared tracking types — Meta Pixel + Google Analytics 4 + consent.
3
+ *
4
+ * Ported from `@1click/ui/src/tracking/types.ts` (v2.3.1) and re-seated on
5
+ * the barter contract:
6
+ *
7
+ * - `TrackingConfig` mirrors the barter store-config `tracking` block
8
+ * served by `GET /api/store/integrations` (code truth:
9
+ * `src/lib/tracking/constants.ts` `StoreTrackingBlock` +
10
+ * `src/lib/tracking/store-block.ts`; contract: store-api.md "Tracking
11
+ * block"). Differences vs the @1click shape: `googleAds` is a barter
12
+ * extension, `consent_required` is new (consent-management card), and
13
+ * `rybbit` is deliberately ABSENT — Rybbit is platform analytics,
14
+ * provisioned from platform env, never a tenant integration; the
15
+ * `<Rybbit>` component takes its props directly from the host app.
16
+ *
17
+ * - Server-side CAPI + GA4 Measurement Protocol sending is
18
+ * barter-backend-owned (the `order.placed` forwarder,
19
+ * `src/lib/tracking/forward.ts`) — this package fires CLIENT events
20
+ * only and writes attribution for the server to inherit.
21
+ *
22
+ * Purchase dedupe contract (THE invariant — admin-api.md §34,
23
+ * `src/lib/tracking/constants.ts:32-40`):
24
+ * - Meta: the backend CAPI Purchase uses
25
+ * `event_id = "purchase_" + order.display_id`. The client Pixel MUST
26
+ * pass the same value as `eventID` on `fbq('track', 'Purchase', …)` —
27
+ * `trackPurchase()` (fbq.ts) builds the id from `order.display_id` so
28
+ * the format can never drift. Meta dedupes by event_name + event_id
29
+ * within ~3 days.
30
+ * - GA4: Measurement Protocol uses
31
+ * `transaction_id = String(order.display_id)`; pass the same value in
32
+ * `trackGAPurchase()` and GA4 dedupes automatically.
33
+ */
34
+
35
+ export type TrackingConfig = {
36
+ facebookPixel?: { pixelId: string }
37
+ gtm?: { containerId: string }
38
+ ga4?: { measurementId: string }
39
+ klaviyo?: { publicKey: string }
40
+ /** Barter extension over the @1click TrackingConfig — explicit Google
41
+ * Ads tag config (the @1click engine wired Ads through GTM). */
42
+ googleAds?: { conversionId: string; conversionLabel?: string }
43
+ /** True when the store's consent CMP is enabled — client tags must
44
+ * mount through the consent gate (`_1c_consent` / Consent Mode v2). */
45
+ consent_required?: boolean
46
+ }
47
+
48
+ /** Envelope of `GET /api/store/integrations` (tracking key only — the
49
+ * endpoint also serves carriers/COD blocks owned by other cards). */
50
+ export type TrackingConfigResponse = {
51
+ tracking?: TrackingConfig
52
+ }
53
+
54
+ /** A single line item passed in `contents`. Meta requires `id` + `quantity`. */
55
+ export type MetaContentItem = {
56
+ id: string
57
+ quantity: number
58
+ item_price?: number
59
+ }
60
+
61
+ export type ViewContentData = {
62
+ content_ids: string[]
63
+ content_type: "product" | "product_group"
64
+ currency: string
65
+ value: number
66
+ }
67
+
68
+ export type AddToCartData = {
69
+ content_ids: string[]
70
+ content_type: "product" | "product_group"
71
+ currency: string
72
+ value: number
73
+ contents: MetaContentItem[]
74
+ }
75
+
76
+ export type InitiateCheckoutData = {
77
+ content_ids: string[]
78
+ content_type: "product" | "product_group"
79
+ currency: string
80
+ value: number
81
+ num_items: number
82
+ contents: MetaContentItem[]
83
+ }
84
+
85
+ export type PurchaseData = {
86
+ content_ids: string[]
87
+ content_type: "product" | "product_group"
88
+ currency: string
89
+ value: number
90
+ num_items: number
91
+ contents: MetaContentItem[]
92
+ }
93
+
94
+ /**
95
+ * Lead event payload — shape per Meta's Lead standard event spec.
96
+ * All fields optional (Meta accepts an empty Lead). Storefront wraps
97
+ * popup signups + newsletter forms in a Lead event so Meta can
98
+ * optimize ad delivery for "likely subscribers" and build Lead
99
+ * lookalike audiences from our funnel.
100
+ */
101
+ export type LeadData = {
102
+ /** Optional lead value (e.g. expected LTV for a subscriber). */
103
+ value?: number
104
+ currency?: string
105
+ /** Free-text label describing what the visitor signed up for —
106
+ * e.g. "newsletter_popup", "newsletter_footer". */
107
+ content_name?: string
108
+ content_category?: string
109
+ }
110
+
111
+ /**
112
+ * Attribution payload written to `cart.metadata` before completion
113
+ * (consent-gated — see store-api.md "Tracking block" wiring contract).
114
+ * Cart completion copies these keys onto `order.metadata` and the barter
115
+ * `order.placed` forwarder reads exactly them to build the CAPI Purchase
116
+ * `user_data` and the GA4 MP Purchase `client_id` / `session_id`
117
+ * (code truth: `src/lib/tracking/constants.ts` TrackingAttributionMeta).
118
+ *
119
+ * - fb_fbp / fb_fbc: from _fbp / _fbc cookies (set by fbq init, or
120
+ * defensively by attribution.ts before the Pixel loads)
121
+ * - fb_user_agent: from request User-Agent header
122
+ * - fb_client_ip: from request x-forwarded-for / x-real-ip
123
+ * - fb_event_source_url: from request Referer
124
+ * - ga_client_id: from _ga cookie value, schema prefix stripped
125
+ * - ga_session_id: parsed from _ga_<MEASUREMENT_ID> cookie's session segment
126
+ * - ga_engagement_time_msec: client-tracked time-on-session in ms
127
+ * (passed in via clientHints on order placement, not derivable server-side)
128
+ */
129
+ export type TrackingAttribution = {
130
+ fb_fbp?: string
131
+ fb_fbc?: string
132
+ fb_user_agent?: string
133
+ fb_client_ip?: string
134
+ fb_event_source_url?: string
135
+ /** Per-browser anonymous visitor id (cookie `_1c_anon`). Backend
136
+ * uses this as `external_id` on the Purchase CAPI when no
137
+ * customer_id is available, AND alongside customer_id when both
138
+ * are known (Meta accepts external_id as an array, so dedup +
139
+ * cross-session attribution both improve). */
140
+ fb_anon_id?: string
141
+ ga_client_id?: string
142
+ ga_session_id?: string
143
+ ga_engagement_time_msec?: number
144
+
145
+ // ── UTM attribution ────────────────────────────────────────────────
146
+ // Captured by browser-side `captureUtmsFromUrl()` (attribution.ts)
147
+ // into `_1c_utm_first` / `_1c_utm_last` cookies, then read here at
148
+ // checkout completion. Flat keys (instead of nested objects) so a
149
+ // raw `SELECT metadata->>'utm_first_source' FROM orders` query
150
+ // works without `jsonb_path_query`. Each tuple's `captured_at` is
151
+ // Unix seconds — `attribution_window_days = (order.created_at_unix
152
+ // - captured_at) / 86400`.
153
+
154
+ /** Acquisition (first-touch) UTM source — the campaign that
155
+ * originally brought this visitor in. 365-day TTL. Stable across
156
+ * multiple ad-click revisits. */
157
+ utm_first_source?: string
158
+ utm_first_medium?: string
159
+ utm_first_campaign?: string
160
+ utm_first_term?: string
161
+ utm_first_content?: string
162
+ utm_first_captured_at?: number
163
+
164
+ /** Closer (last-touch) UTM source — the campaign whose click
165
+ * immediately preceded this order. 90-day TTL, refreshed on every
166
+ * UTM-bearing visit. Aligns with Meta / Google Ads attribution
167
+ * windows. */
168
+ utm_last_source?: string
169
+ utm_last_medium?: string
170
+ utm_last_campaign?: string
171
+ utm_last_term?: string
172
+ utm_last_content?: string
173
+ utm_last_captured_at?: number
174
+ }
175
+
176
+ /**
177
+ * Client-only attribution data — must be passed through to order
178
+ * placement by the storefront because it can't be read from
179
+ * cookies/headers on the server (the browser computes it during the
180
+ * session).
181
+ */
182
+ export type TrackingClientHints = {
183
+ /** Accumulated time-on-session in ms (Date.now() - sessionStart). */
184
+ engagementTimeMsec?: number
185
+ }
@@ -0,0 +1,58 @@
1
+ "use client"
2
+
3
+ /**
4
+ * Engagement-time helpers (client only).
5
+ *
6
+ * The GA4 Measurement Protocol Purchase event needs
7
+ * `ga_engagement_time_msec` — how long the user spent in the session
8
+ * before placing the order. This can't be read from a cookie; it has
9
+ * to be tracked by the browser.
10
+ *
11
+ * Strategy: store `sessionStartTs` in `sessionStorage` on first read.
12
+ * On every subsequent call, return `Date.now() - sessionStartTs`.
13
+ * sessionStorage scopes to the tab — closing the tab resets the
14
+ * session, which is the desired behaviour.
15
+ *
16
+ * Cap at 30 minutes to filter out idle/dead-tab sessions. GA4's own
17
+ * session timeout default is 30 minutes, so this matches.
18
+ */
19
+
20
+ const SESSION_KEY = "tracking_session_start"
21
+ const MAX_ENGAGEMENT_MS = 30 * 60 * 1000 // 30 minutes
22
+
23
+ /**
24
+ * Returns the current engagement time in milliseconds for this session.
25
+ * Returns 0 in non-browser contexts. Always returns a number ≥ 0.
26
+ */
27
+ export function getEngagementTimeMsec(): number {
28
+ if (typeof window === "undefined") return 0
29
+ try {
30
+ let start = window.sessionStorage.getItem(SESSION_KEY)
31
+ if (!start) {
32
+ start = String(Date.now())
33
+ window.sessionStorage.setItem(SESSION_KEY, start)
34
+ }
35
+ const elapsed = Date.now() - Number(start)
36
+ if (!Number.isFinite(elapsed) || elapsed < 0) return 0
37
+ return Math.min(elapsed, MAX_ENGAGEMENT_MS)
38
+ } catch {
39
+ return 0
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Initialise the session start timestamp now (no return value). Call
45
+ * from the root layout's MetaPixel/GA4 wrapper so the timer starts on
46
+ * first page view of the session, not on first checkout.
47
+ */
48
+ export function initEngagementTime(): void {
49
+ if (typeof window === "undefined") return
50
+ try {
51
+ if (!window.sessionStorage.getItem(SESSION_KEY)) {
52
+ window.sessionStorage.setItem(SESSION_KEY, String(Date.now()))
53
+ }
54
+ } catch {
55
+ // sessionStorage unavailable — Safari private mode etc. Engagement
56
+ // time will be 0 in that case, which the backend treats as "unknown".
57
+ }
58
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @cartbase/storefront Tailwind preset — shadcn/ui standard token structure
3
+ * (ported verbatim from @1click/ui tailwind-preset.cjs v2.3.1)
4
+ *
5
+ * Uses oklch color space (modern CSS standard, matches shadcn/ui Create output).
6
+ * CSS variables store raw oklch channels so Tailwind's opacity modifiers work:
7
+ * --primary: 0.553 0.195 38.402;
8
+ * → bg-primary = oklch(0.553 0.195 38.402)
9
+ * → bg-primary/50 = oklch(0.553 0.195 38.402 / 50%)
10
+ */
11
+
12
+ /** @type {import('tailwindcss').Config} */
13
+ module.exports = {
14
+ theme: {
15
+ extend: {
16
+ colors: {
17
+ background: "oklch(var(--background) / <alpha-value>)",
18
+ foreground: "oklch(var(--foreground) / <alpha-value>)",
19
+ card: {
20
+ DEFAULT: "oklch(var(--card) / <alpha-value>)",
21
+ foreground: "oklch(var(--card-foreground) / <alpha-value>)",
22
+ },
23
+ popover: {
24
+ DEFAULT: "oklch(var(--popover) / <alpha-value>)",
25
+ foreground: "oklch(var(--popover-foreground) / <alpha-value>)",
26
+ },
27
+ primary: {
28
+ DEFAULT: "oklch(var(--primary) / <alpha-value>)",
29
+ foreground: "oklch(var(--primary-foreground) / <alpha-value>)",
30
+ },
31
+ secondary: {
32
+ DEFAULT: "oklch(var(--secondary) / <alpha-value>)",
33
+ foreground: "oklch(var(--secondary-foreground) / <alpha-value>)",
34
+ },
35
+ muted: {
36
+ DEFAULT: "oklch(var(--muted) / <alpha-value>)",
37
+ foreground: "oklch(var(--muted-foreground) / <alpha-value>)",
38
+ },
39
+ accent: {
40
+ DEFAULT: "oklch(var(--accent) / <alpha-value>)",
41
+ foreground: "oklch(var(--accent-foreground) / <alpha-value>)",
42
+ },
43
+ destructive: {
44
+ DEFAULT: "oklch(var(--destructive) / <alpha-value>)",
45
+ foreground: "oklch(var(--destructive-foreground) / <alpha-value>)",
46
+ },
47
+ border: "oklch(var(--border) / <alpha-value>)",
48
+ input: "oklch(var(--input) / <alpha-value>)",
49
+ ring: "oklch(var(--ring) / <alpha-value>)",
50
+ success: {
51
+ DEFAULT: "oklch(var(--success) / <alpha-value>)",
52
+ foreground: "oklch(var(--success-foreground) / <alpha-value>)",
53
+ },
54
+ warning: {
55
+ DEFAULT: "oklch(var(--warning) / <alpha-value>)",
56
+ foreground: "oklch(var(--warning-foreground) / <alpha-value>)",
57
+ },
58
+ },
59
+ fontFamily: {
60
+ display: ["var(--font-display)", "serif"],
61
+ body: ["var(--font-body)", "system-ui", "sans-serif"],
62
+ mono: ["var(--font-mono)", "ui-monospace", "monospace"],
63
+ },
64
+ borderRadius: {
65
+ lg: "var(--radius)",
66
+ md: "calc(var(--radius) - 2px)",
67
+ sm: "calc(var(--radius) - 4px)",
68
+ },
69
+ },
70
+ },
71
+ plugins: [],
72
+ }