@develemit/storefront 0.0.0-stage → 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.
Files changed (40) hide show
  1. package/README.md +265 -2
  2. package/dist/cart/cart-provider.d.ts +24 -0
  3. package/dist/cart/cart-reducer.d.ts +31 -0
  4. package/dist/cart/cart-storage.d.ts +6 -0
  5. package/dist/client/commerce-client.d.ts +13 -0
  6. package/dist/client/errors.d.ts +12 -0
  7. package/dist/client/index.d.ts +2 -0
  8. package/dist/client/index.js +64 -0
  9. package/dist/client/index.js.map +1 -0
  10. package/dist/components/add-to-cart-button/add-to-cart-button.d.ts +14 -0
  11. package/dist/components/availability-badge/availability-badge.d.ts +15 -0
  12. package/dist/components/cart-button/cart-button.d.ts +9 -0
  13. package/dist/components/cart-drawer/cart-drawer.d.ts +18 -0
  14. package/dist/components/cart-drawer/use-drawer-presence.d.ts +5 -0
  15. package/dist/components/cart-line-item/cart-line-item.d.ts +11 -0
  16. package/dist/components/cart-summary/cart-summary.d.ts +10 -0
  17. package/dist/components/category-filter/category-filter.d.ts +11 -0
  18. package/dist/components/fixtures.d.ts +2 -0
  19. package/dist/components/price-tag/price-tag.d.ts +7 -0
  20. package/dist/components/product-card/product-card.d.ts +9 -0
  21. package/dist/components/product-gallery/product-gallery.d.ts +6 -0
  22. package/dist/components/product-grid/product-grid.d.ts +10 -0
  23. package/dist/components/render-with-cart.d.ts +3 -0
  24. package/dist/format/index.d.ts +2 -0
  25. package/dist/format/index.js +1 -0
  26. package/dist/index.d.ts +22 -0
  27. package/dist/index.js +686 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/lib/format-price.d.ts +1 -0
  30. package/dist/lib/parse-duration.d.ts +1 -0
  31. package/dist/lib/provider.d.ts +28 -0
  32. package/dist/lib/theme.d.ts +15 -0
  33. package/dist/sort-for-display-Cj6GMeCY.js +15 -0
  34. package/dist/sort-for-display-Cj6GMeCY.js.map +1 -0
  35. package/dist/styles.css +476 -0
  36. package/dist/views/featured-products.d.ts +8 -0
  37. package/dist/views/product-detail-view.d.ts +13 -0
  38. package/dist/views/shop-view.d.ts +21 -0
  39. package/dist/views/sort-for-display.d.ts +2 -0
  40. package/package.json +46 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["_Fragment","_Fragment","_Fragment","formatPrice","VAR_NAMES","themeToCssVars","vars","key","value","theme","defaults","priority","slug","StorefrontContext","createContext","StorefrontProvider","overrides","hrefs","useStorefront","useContext","PriceTag","classes","sold","className","priceCents","currency","locale","defaultAvailabilityLabels","AvailabilityBadge","text","labels","variant","label","availability","quantity","ProductCard","product","image","renderLink","Fragment","renderImage","ProductGrid","products","emptyState","index","priorityCount","CategoryFilter","ordered","sortCategories","categories","controlled","onSelect","item","active","selected","allLabel","c","formatLabel","ProductGallery","setActive","useState","images","event","i","ShopView","slots","activeCategory","formatCategoryLabel","sortForDisplay","nextHref","loadMoreLabel","ProductDetailView","FeaturedProducts","heading","emptyCart","addLine","first","state","line","existing","l","cartReducer","action","subtotalCents","sum","itemCount","localStorageCartStorage","raw","parsed","result","cartLineSchema","CartContext","defaultStorage","CartProvider","dispatch","useReducer","hydrated","setHydrated","isOpen","setOpen","stateRef","useRef","useEffect","cancelled","storage","loaded","add","useCallback","useMemo","useCart","ctx","defaultAddToCartLabels","AddToCartButton","cart","inCart","unavailable","atCap","disabled","handleClick","openOnAdd","defaultCartLineItemLabels","CartLineItem","editable","setQuantity","remove","parseDurationMs","match","amount","useDrawerPresence","durationMs","setDurationMs","wasOpen","setWasOpen","lingering","setLingering","backdropRef","timer","CartSummary","subtotal","checkoutDisabled","onCheckout","checkoutLabel","defaultCartDrawerLabels","FOCUSABLE","CartDrawer","panelRef","presence","trigger","handleKeyDown","items","last","summary","CartButton","count","showCount","hideZeroCount","ariaLabel","labelHidden","icon"],"sources":["../src/lib/theme.ts","../src/lib/provider.tsx","../src/components/price-tag/price-tag.tsx","../src/components/availability-badge/availability-badge.tsx","../src/components/product-card/product-card.tsx","../src/components/product-grid/product-grid.tsx","../src/components/category-filter/category-filter.tsx","../src/components/product-gallery/product-gallery.tsx","../src/views/shop-view.tsx","../src/views/product-detail-view.tsx","../src/views/featured-products.tsx","../src/cart/cart-reducer.ts","../src/cart/cart-storage.ts","../src/cart/cart-provider.tsx","../src/components/add-to-cart-button/add-to-cart-button.tsx","../src/components/cart-line-item/cart-line-item.tsx","../src/lib/parse-duration.ts","../src/components/cart-drawer/use-drawer-presence.ts","../src/components/cart-summary/cart-summary.tsx","../src/components/cart-drawer/cart-drawer.tsx","../src/components/cart-button/cart-button.tsx"],"sourcesContent":["export interface StorefrontTheme {\n colorBg?: string;\n colorSurface?: string;\n colorFg?: string;\n colorMuted?: string;\n colorAccent?: string;\n colorAccentFg?: string;\n colorBorder?: string;\n colorSold?: string;\n radius?: string;\n fontDisplay?: string;\n fontBody?: string;\n spaceUnit?: string;\n}\n\nconst VAR_NAMES: Record<keyof StorefrontTheme, string> = {\n colorBg: '--sf-color-bg',\n colorSurface: '--sf-color-surface',\n colorFg: '--sf-color-fg',\n colorMuted: '--sf-color-muted',\n colorAccent: '--sf-color-accent',\n colorAccentFg: '--sf-color-accent-fg',\n colorBorder: '--sf-color-border',\n colorSold: '--sf-color-sold',\n radius: '--sf-radius',\n fontDisplay: '--sf-font-display',\n fontBody: '--sf-font-body',\n spaceUnit: '--sf-space-unit',\n};\n\nexport function themeToCssVars(theme: StorefrontTheme): Record<string, string> {\n const vars: Record<string, string> = {};\n for (const key of Object.keys(VAR_NAMES) as (keyof StorefrontTheme)[]) {\n const value = theme[key];\n if (value !== undefined) vars[VAR_NAMES[key]] = value;\n }\n return vars;\n}\n","import { createContext, useContext, type ReactNode } from 'react';\n\nexport interface LinkProps {\n href: string;\n children: ReactNode;\n className?: string;\n}\n\nexport interface ImageProps {\n src: string;\n alt: string;\n sizes?: string;\n className?: string;\n priority?: boolean;\n}\n\nexport interface StorefrontConfig {\n renderLink: (props: LinkProps) => ReactNode;\n renderImage: (props: ImageProps) => ReactNode;\n locale: string;\n hrefs: {\n product: (slug: string) => string;\n category: (slug: string) => string;\n };\n}\n\nconst defaults: StorefrontConfig = {\n renderLink: ({ href, children, className }) => (\n <a href={href} className={className}>\n {children}\n </a>\n ),\n renderImage: ({ src, alt, sizes, className, priority }) => (\n <img\n src={src}\n alt={alt}\n sizes={sizes}\n className={className}\n loading={priority ? 'eager' : 'lazy'}\n decoding=\"async\"\n fetchPriority={priority ? 'high' : undefined}\n />\n ),\n locale: 'en-US',\n hrefs: {\n product: (slug) => `/shop/${slug}`,\n category: (slug) => `/shop?category=${slug}`,\n },\n};\n\nconst StorefrontContext = createContext<StorefrontConfig>(defaults);\n\nexport interface StorefrontProviderProps extends Partial<\n Omit<StorefrontConfig, 'hrefs'>\n> {\n hrefs?: Partial<StorefrontConfig['hrefs']>;\n children: ReactNode;\n}\n\nexport function StorefrontProvider({\n children,\n hrefs,\n ...overrides\n}: StorefrontProviderProps) {\n const value: StorefrontConfig = {\n ...defaults,\n ...overrides,\n hrefs: { ...defaults.hrefs, ...hrefs },\n };\n return (\n <StorefrontContext.Provider value={value}>\n {children}\n </StorefrontContext.Provider>\n );\n}\n\nexport function useStorefront(): StorefrontConfig {\n return useContext(StorefrontContext);\n}\n","import { formatPrice } from '../../lib/format-price';\nimport { useStorefront } from '../../lib/provider';\n\nexport interface PriceTagProps {\n priceCents: number;\n currency: string;\n sold?: boolean;\n className?: string;\n}\n\nexport function PriceTag({\n priceCents,\n currency,\n sold = false,\n className,\n}: PriceTagProps) {\n const { locale } = useStorefront();\n const classes = ['sf-price', sold && 'sf-price--sold', className]\n .filter(Boolean)\n .join(' ');\n return (\n <span className={classes}>{formatPrice(priceCents, currency, locale)}</span>\n );\n}\n","import type { ProductAvailability } from '@develemit/commerce-contracts';\n\nexport interface AvailabilityLabels {\n oneOfAKind: string;\n sold: string;\n reserved: string;\n available: string;\n}\n\nexport const defaultAvailabilityLabels: AvailabilityLabels = {\n oneOfAKind: 'One of a kind',\n sold: 'Sold',\n reserved: 'On hold',\n available: 'Available',\n};\n\nexport interface AvailabilityBadgeProps {\n availability: ProductAvailability;\n quantity: number;\n labels?: Partial<AvailabilityLabels>;\n className?: string;\n}\n\nexport function AvailabilityBadge({\n availability,\n quantity,\n labels,\n className,\n}: AvailabilityBadgeProps) {\n const text = { ...defaultAvailabilityLabels, ...labels };\n let variant: string;\n let label: string;\n if (availability === 'sold') {\n variant = 'sold';\n label = text.sold;\n } else if (availability === 'reserved') {\n variant = 'reserved';\n label = text.reserved;\n } else if (quantity === 1) {\n variant = 'unique';\n label = text.oneOfAKind;\n } else {\n variant = 'available';\n label = text.available;\n }\n const classes = ['sf-badge', `sf-badge--${variant}`, className]\n .filter(Boolean)\n .join(' ');\n return (\n <span role=\"status\" className={classes}>\n {label}\n </span>\n );\n}\n","import type { ProductCard as ProductCardData } from '@develemit/commerce-contracts';\nimport { useStorefront } from '../../lib/provider';\nimport {\n AvailabilityBadge,\n type AvailabilityLabels,\n} from '../availability-badge/availability-badge';\nimport { PriceTag } from '../price-tag/price-tag';\n\nexport interface ProductCardProps {\n product: ProductCardData;\n labels?: Partial<AvailabilityLabels>;\n sizes?: string;\n priority?: boolean;\n}\n\nexport function ProductCard({\n product,\n labels,\n sizes,\n priority,\n}: ProductCardProps) {\n const { renderLink, renderImage, hrefs } = useStorefront();\n const sold = product.availability === 'sold';\n const image = product.images[0];\n const classes = ['sf-card', sold && 'sf-card--sold']\n .filter(Boolean)\n .join(' ');\n return (\n <article className={classes}>\n {renderLink({\n href: hrefs.product(product.slug),\n className: 'sf-card__link',\n children: (\n <>\n <div className=\"sf-card__media\">\n {image ? (\n renderImage({\n src: image.url,\n alt: image.alt,\n sizes,\n className: 'sf-card__image',\n priority,\n })\n ) : (\n <div className=\"sf-card__placeholder\" aria-hidden=\"true\" />\n )}\n </div>\n <div className=\"sf-card__body\">\n <h3 className=\"sf-card__title\">{product.name}</h3>\n <PriceTag\n priceCents={product.priceCents}\n currency={product.currency}\n sold={sold}\n />\n <AvailabilityBadge\n availability={product.availability}\n quantity={product.quantity}\n labels={labels}\n />\n </div>\n </>\n ),\n })}\n </article>\n );\n}\n","import type { ReactNode } from 'react';\nimport type { ProductCard as ProductCardData } from '@develemit/commerce-contracts';\nimport type { AvailabilityLabels } from '../availability-badge/availability-badge';\nimport { ProductCard } from '../product-card/product-card';\n\nexport interface ProductGridProps {\n products: ProductCardData[];\n emptyState?: ReactNode;\n labels?: Partial<AvailabilityLabels>;\n priorityCount?: number;\n}\n\nexport function ProductGrid({\n products,\n emptyState,\n labels,\n priorityCount = 0,\n}: ProductGridProps) {\n if (products.length === 0) {\n return (\n <div className=\"sf-grid-empty\">{emptyState ?? 'No products found.'}</div>\n );\n }\n return (\n <ul className=\"sf-grid\">\n {products.map((product, index) => (\n <li key={product.id} className=\"sf-grid__item\">\n <ProductCard\n product={product}\n labels={labels}\n priority={index < priorityCount}\n />\n </li>\n ))}\n </ul>\n );\n}\n","import type { Category } from '@develemit/commerce-contracts';\nimport { useStorefront } from '../../lib/provider';\n\nexport interface CategoryFilterProps {\n categories: Category[];\n selected?: string;\n onSelect?: (slug: string | undefined) => void;\n allLabel?: string;\n label?: string;\n formatLabel?: (category: Category) => string;\n sortCategories?: (a: Category, b: Category) => number;\n}\n\nexport function CategoryFilter({\n categories,\n selected,\n onSelect,\n allLabel = 'All',\n label = 'Categories',\n formatLabel,\n sortCategories,\n}: CategoryFilterProps) {\n const { renderLink, hrefs } = useStorefront();\n const ordered = sortCategories\n ? [...categories].sort(sortCategories)\n : categories;\n const controlled = onSelect !== undefined;\n\n const item = (slug: string | undefined, text: string, href: string) => {\n const active = selected === slug;\n const className = `sf-filter__item${active ? ' sf-filter__item--active' : ''}`;\n return (\n <li key={slug ?? '__all'}>\n {controlled ? (\n <button\n type=\"button\"\n className={className}\n aria-pressed={active}\n onClick={() => onSelect(slug)}\n >\n {text}\n </button>\n ) : (\n renderLink({\n href,\n className,\n children: text,\n })\n )}\n </li>\n );\n };\n\n return (\n <nav aria-label={label} className=\"sf-filter\">\n <ul className=\"sf-filter__list\">\n {item(undefined, allLabel, hrefs.category(''))}\n {ordered.map((c) =>\n item(\n c.slug,\n formatLabel ? formatLabel(c) : c.label,\n hrefs.category(c.slug),\n ),\n )}\n </ul>\n </nav>\n );\n}\n","import { useState, type KeyboardEvent } from 'react';\nimport type { ProductImage } from '@develemit/commerce-contracts';\nimport { useStorefront } from '../../lib/provider';\n\nexport interface ProductGalleryProps {\n images: ProductImage[];\n sizes?: string;\n}\n\nexport function ProductGallery({ images, sizes }: ProductGalleryProps) {\n const { renderImage } = useStorefront();\n const [active, setActive] = useState(0);\n\n if (images.length === 0) return null;\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'ArrowRight') {\n setActive((i) => (i + 1) % images.length);\n } else if (event.key === 'ArrowLeft') {\n setActive((i) => (i - 1 + images.length) % images.length);\n } else if (event.key === 'Home') {\n setActive(0);\n } else if (event.key === 'End') {\n setActive(images.length - 1);\n } else {\n return;\n }\n event.preventDefault();\n };\n\n return (\n <div\n className=\"sf-gallery\"\n role=\"group\"\n aria-roledescription=\"carousel\"\n aria-label=\"Product images\"\n tabIndex={0}\n onKeyDown={onKeyDown}\n >\n <div className=\"sf-gallery__stage\">\n {images.map((image, index) => (\n <div\n key={image.url}\n className=\"sf-gallery__slide\"\n hidden={index !== active}\n >\n {renderImage({\n src: image.url,\n alt: image.alt,\n sizes,\n priority: index === active,\n className: 'sf-gallery__image',\n })}\n </div>\n ))}\n </div>\n {images.length > 1 && (\n <div className=\"sf-gallery__thumbs\">\n {images.map((image, index) => (\n <button\n key={image.url}\n type=\"button\"\n className={`sf-gallery__thumb${index === active ? ' sf-gallery__thumb--active' : ''}`}\n aria-label={`Show image ${index + 1} of ${images.length}`}\n aria-current={index === active}\n onClick={() => setActive(index)}\n >\n {renderImage({\n src: image.url,\n alt: '',\n className: 'sf-gallery__thumb-image',\n })}\n </button>\n ))}\n </div>\n )}\n </div>\n );\n}\n","import type { ReactNode } from 'react';\nimport type { Category, ProductCard } from '@develemit/commerce-contracts';\nimport { CategoryFilter } from '../components/category-filter/category-filter';\nimport { ProductGrid } from '../components/product-grid/product-grid';\nimport type { AvailabilityLabels } from '../components/availability-badge/availability-badge';\nimport { useStorefront } from '../lib/provider';\nimport { sortForDisplay } from './sort-for-display';\n\nexport interface ShopViewSlots {\n header?: ReactNode;\n intro?: ReactNode;\n emptyState?: ReactNode;\n}\n\nexport interface ShopViewProps {\n products: ProductCard[];\n categories: Category[];\n activeCategory?: string;\n slots?: ShopViewSlots;\n labels?: Partial<AvailabilityLabels>;\n allLabel?: string;\n formatCategoryLabel?: (category: Category) => string;\n sortCategories?: (a: Category, b: Category) => number;\n nextHref?: string;\n loadMoreLabel?: string;\n}\n\nexport function ShopView({\n products,\n categories,\n activeCategory,\n slots = {},\n labels,\n allLabel,\n formatCategoryLabel,\n sortCategories,\n nextHref,\n loadMoreLabel = 'Load more',\n}: ShopViewProps) {\n const { renderLink } = useStorefront();\n return (\n <div className=\"sf-shop\">\n {slots.header}\n {slots.intro}\n <CategoryFilter\n categories={categories}\n selected={activeCategory}\n allLabel={allLabel}\n formatLabel={formatCategoryLabel}\n sortCategories={sortCategories}\n />\n <ProductGrid\n products={sortForDisplay(products)}\n emptyState={slots.emptyState}\n labels={labels}\n priorityCount={6}\n />\n {nextHref && (\n <div className=\"sf-shop__pager\">\n {renderLink({\n href: nextHref,\n className: 'sf-shop__more',\n children: loadMoreLabel,\n })}\n </div>\n )}\n </div>\n );\n}\n","import type { ReactNode } from 'react';\nimport type { Product } from '@develemit/commerce-contracts';\nimport {\n AvailabilityBadge,\n type AvailabilityLabels,\n} from '../components/availability-badge/availability-badge';\nimport { PriceTag } from '../components/price-tag/price-tag';\nimport { ProductGallery } from '../components/product-gallery/product-gallery';\n\nexport interface ProductDetailViewSlots {\n actions?: ReactNode;\n afterDetails?: ReactNode;\n}\n\nexport interface ProductDetailViewProps {\n product: Product;\n slots?: ProductDetailViewSlots;\n labels?: Partial<AvailabilityLabels>;\n}\n\nexport function ProductDetailView({\n product,\n slots = {},\n labels,\n}: ProductDetailViewProps) {\n return (\n <article className=\"sf-detail\">\n <ProductGallery images={product.images} />\n <div className=\"sf-detail__body\">\n <h1 className=\"sf-detail__title\">{product.name}</h1>\n <PriceTag priceCents={product.priceCents} currency={product.currency} />\n <AvailabilityBadge\n availability={product.availability}\n quantity={product.quantity}\n labels={labels}\n />\n <p className=\"sf-detail__description\">{product.description}</p>\n {(product.materials || product.dimensions) && (\n <dl className=\"sf-detail__specs\">\n {product.materials && (\n <>\n <dt>Materials</dt>\n <dd>{product.materials}</dd>\n </>\n )}\n {product.dimensions && (\n <>\n <dt>Dimensions</dt>\n <dd>{product.dimensions}</dd>\n </>\n )}\n </dl>\n )}\n {slots.actions && (\n <div className=\"sf-detail__actions\">{slots.actions}</div>\n )}\n {slots.afterDetails}\n </div>\n </article>\n );\n}\n","import type { ProductCard } from '@develemit/commerce-contracts';\nimport type { AvailabilityLabels } from '../components/availability-badge/availability-badge';\nimport { ProductGrid } from '../components/product-grid/product-grid';\n\nexport interface FeaturedProductsProps {\n products: ProductCard[];\n heading?: string;\n labels?: Partial<AvailabilityLabels>;\n}\n\nexport function FeaturedProducts({\n products,\n heading = 'Featured',\n labels,\n}: FeaturedProductsProps) {\n if (products.length === 0) return null;\n return (\n <section className=\"sf-featured\">\n <h2 className=\"sf-featured__heading\">{heading}</h2>\n <ProductGrid products={products} labels={labels} priorityCount={6} />\n </section>\n );\n}\n","import type { CartLine } from '@develemit/commerce-contracts';\n\nexport interface CartState {\n lines: CartLine[];\n}\n\nexport type CartAction =\n | { type: 'add'; line: CartLine }\n | { type: 'remove'; productId: string }\n | { type: 'setQuantity'; productId: string; quantity: number }\n | { type: 'clear' }\n | { type: 'hydrate'; state: CartState };\n\nexport interface AddResult {\n state: CartState;\n added: boolean;\n capped: boolean;\n reason?: 'currency-mismatch';\n}\n\nexport const emptyCart: CartState = { lines: [] };\n\nexport function addLine(state: CartState, line: CartLine): AddResult {\n const first = state.lines[0];\n if (first && first.currency !== line.currency) {\n return { state, added: false, capped: false, reason: 'currency-mismatch' };\n }\n const existing = state.lines.find((l) => l.productId === line.productId);\n if (!existing) {\n const quantity = Math.min(line.quantity, line.maxQuantity);\n return {\n state: { lines: [...state.lines, { ...line, quantity }] },\n added: true,\n capped: quantity < line.quantity,\n };\n }\n if (existing.quantity >= existing.maxQuantity) {\n return { state, added: false, capped: true };\n }\n const quantity = Math.min(\n existing.quantity + line.quantity,\n existing.maxQuantity,\n );\n return {\n state: {\n lines: state.lines.map((l) =>\n l.productId === line.productId ? { ...l, quantity } : l,\n ),\n },\n added: true,\n capped: existing.quantity + line.quantity > existing.maxQuantity,\n };\n}\n\nexport function cartReducer(state: CartState, action: CartAction): CartState {\n switch (action.type) {\n case 'add':\n return addLine(state, action.line).state;\n case 'remove':\n return {\n lines: state.lines.filter((l) => l.productId !== action.productId),\n };\n case 'setQuantity': {\n if (action.quantity <= 0) {\n return cartReducer(state, {\n type: 'remove',\n productId: action.productId,\n });\n }\n return {\n lines: state.lines.map((l) =>\n l.productId === action.productId\n ? { ...l, quantity: Math.min(action.quantity, l.maxQuantity) }\n : l,\n ),\n };\n }\n case 'clear':\n return emptyCart;\n case 'hydrate':\n return action.state;\n }\n}\n\nexport function subtotalCents(state: CartState): number {\n return state.lines.reduce((sum, l) => sum + l.priceCents * l.quantity, 0);\n}\n\nexport function itemCount(state: CartState): number {\n return state.lines.reduce((sum, l) => sum + l.quantity, 0);\n}\n","import { cartLineSchema } from '@develemit/commerce-contracts';\nimport { emptyCart, type CartState } from './cart-reducer';\n\nexport interface CartStorage {\n load: () => CartState | Promise<CartState>;\n save: (state: CartState) => void | Promise<void>;\n}\n\nexport function localStorageCartStorage(key = 'sf-cart'): CartStorage {\n return {\n load() {\n if (typeof window === 'undefined') return emptyCart;\n try {\n const raw = window.localStorage.getItem(key);\n if (!raw) return emptyCart;\n const parsed: unknown = JSON.parse(raw);\n const lines = Array.isArray((parsed as CartState)?.lines)\n ? (parsed as CartState).lines\n : [];\n const valid = lines.flatMap((line) => {\n const result = cartLineSchema.safeParse(line);\n return result.success ? [result.data] : [];\n });\n return { lines: valid };\n } catch {\n return emptyCart;\n }\n },\n save(state) {\n if (typeof window === 'undefined') return;\n try {\n window.localStorage.setItem(key, JSON.stringify(state));\n } catch {\n // storage full or blocked: the cart simply stays in memory\n }\n },\n };\n}\n","import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useReducer,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\nimport type { CartLine } from '@develemit/commerce-contracts';\nimport {\n addLine,\n cartReducer,\n emptyCart,\n itemCount,\n subtotalCents,\n type AddResult,\n type CartState,\n} from './cart-reducer';\nimport { localStorageCartStorage, type CartStorage } from './cart-storage';\n\nexport interface CartContextValue {\n lines: CartLine[];\n itemCount: number;\n subtotalCents: number;\n currency: string | null;\n hydrated: boolean;\n isOpen: boolean;\n open: () => void;\n close: () => void;\n add: (line: CartLine) => AddResult;\n remove: (productId: string) => void;\n setQuantity: (productId: string, quantity: number) => void;\n clear: () => void;\n}\n\nconst CartContext = createContext<CartContextValue | null>(null);\n\nexport interface CartProviderProps {\n storage?: CartStorage;\n children: ReactNode;\n}\n\nconst defaultStorage = localStorageCartStorage();\n\nexport function CartProvider({\n storage = defaultStorage,\n children,\n}: CartProviderProps) {\n const [state, dispatch] = useReducer(cartReducer, emptyCart);\n const [hydrated, setHydrated] = useState(false);\n const [isOpen, setOpen] = useState(false);\n const stateRef = useRef<CartState>(state);\n useEffect(() => {\n stateRef.current = state;\n }, [state]);\n\n useEffect(() => {\n let cancelled = false;\n Promise.resolve(storage.load()).then((loaded) => {\n if (cancelled) return;\n dispatch({ type: 'hydrate', state: loaded });\n setHydrated(true);\n });\n return () => {\n cancelled = true;\n };\n }, [storage]);\n\n useEffect(() => {\n if (hydrated) void storage.save(state);\n }, [hydrated, state, storage]);\n\n const add = useCallback((line: CartLine) => {\n const result = addLine(stateRef.current, line);\n if (result.added) {\n stateRef.current = result.state;\n dispatch({ type: 'add', line });\n }\n return result;\n }, []);\n\n const value = useMemo<CartContextValue>(\n () => ({\n lines: state.lines,\n itemCount: itemCount(state),\n subtotalCents: subtotalCents(state),\n currency: state.lines[0]?.currency ?? null,\n hydrated,\n isOpen,\n open: () => setOpen(true),\n close: () => setOpen(false),\n add,\n remove: (productId) => dispatch({ type: 'remove', productId }),\n setQuantity: (productId, quantity) =>\n dispatch({ type: 'setQuantity', productId, quantity }),\n clear: () => dispatch({ type: 'clear' }),\n }),\n [state, hydrated, isOpen, add],\n );\n\n return <CartContext.Provider value={value}>{children}</CartContext.Provider>;\n}\n\nexport function useCart(): CartContextValue {\n const ctx = useContext(CartContext);\n if (!ctx) throw new Error('useCart must be used inside <CartProvider>');\n return ctx;\n}\n","import type { Product } from '@develemit/commerce-contracts';\nimport { useCart } from '../../cart/cart-provider';\n\nexport interface AddToCartLabels {\n add: string;\n inCart: string;\n unavailable: string;\n}\n\nexport const defaultAddToCartLabels: AddToCartLabels = {\n add: 'Add to cart',\n inCart: 'In your cart',\n unavailable: 'Unavailable',\n};\n\nexport interface AddToCartButtonProps {\n product: Product;\n labels?: Partial<AddToCartLabels>;\n openOnAdd?: boolean;\n className?: string;\n}\n\nexport function AddToCartButton({\n product,\n labels,\n openOnAdd = true,\n className,\n}: AddToCartButtonProps) {\n const cart = useCart();\n const text = { ...defaultAddToCartLabels, ...labels };\n const inCart =\n cart.lines.find((l) => l.productId === product.id)?.quantity ?? 0;\n const unavailable = product.availability !== 'available';\n const atCap = inCart >= product.quantity;\n const disabled = !cart.hydrated || unavailable || atCap;\n const label = unavailable ? text.unavailable : atCap ? text.inCart : text.add;\n\n function handleClick() {\n const result = cart.add({\n productId: product.id,\n slug: product.slug,\n name: product.name,\n priceCents: product.priceCents,\n currency: product.currency,\n imageUrl: product.images[0]?.url,\n quantity: 1,\n maxQuantity: product.quantity,\n });\n if (result.added && openOnAdd) cart.open();\n }\n\n return (\n <button\n type=\"button\"\n className={['sf-button', className].filter(Boolean).join(' ')}\n disabled={disabled}\n onClick={handleClick}\n >\n {label}\n </button>\n );\n}\n","import type { CartLine } from '@develemit/commerce-contracts';\nimport { useCart } from '../../cart/cart-provider';\nimport { formatPrice } from '../../lib/format-price';\nimport { useStorefront } from '../../lib/provider';\n\nexport interface CartLineItemLabels {\n remove: string;\n quantity: string;\n}\n\nexport const defaultCartLineItemLabels: CartLineItemLabels = {\n remove: 'Remove',\n quantity: 'Quantity',\n};\n\nexport interface CartLineItemProps {\n line: CartLine;\n labels?: Partial<CartLineItemLabels>;\n}\n\nexport function CartLineItem({ line, labels }: CartLineItemProps) {\n const { remove, setQuantity } = useCart();\n const { locale, renderLink, renderImage, hrefs } = useStorefront();\n const text = { ...defaultCartLineItemLabels, ...labels };\n const editable = line.maxQuantity > 1;\n return (\n <li className=\"sf-cart-line\">\n {line.imageUrl &&\n renderImage({\n src: line.imageUrl,\n alt: '',\n sizes: '80px',\n className: 'sf-cart-line__image',\n })}\n <div className=\"sf-cart-line__body\">\n {renderLink({\n href: hrefs.product(line.slug),\n className: 'sf-cart-line__name',\n children: line.name,\n })}\n <span className=\"sf-cart-line__price\">\n {formatPrice(line.priceCents * line.quantity, line.currency, locale)}\n </span>\n {editable && (\n <label className=\"sf-cart-line__qty\">\n {text.quantity}\n <input\n type=\"number\"\n min={1}\n max={line.maxQuantity}\n value={line.quantity}\n onChange={(e) =>\n setQuantity(line.productId, Number(e.target.value))\n }\n />\n </label>\n )}\n <button\n type=\"button\"\n className=\"sf-link-button sf-cart-line__remove\"\n onClick={() => remove(line.productId)}\n aria-label={`${text.remove} ${line.name}`}\n >\n {text.remove}\n </button>\n </div>\n </li>\n );\n}\n","export function parseDurationMs(value: string): number {\n const match = /^\\s*(-?\\d*\\.?\\d+)\\s*(ms|s)\\s*$/.exec(value);\n if (!match) return 0;\n const amount = Number(match[1]);\n return Math.max(0, match[2] === 's' ? amount * 1000 : amount);\n}\n","import { useEffect, useState, type RefObject } from 'react';\nimport { parseDurationMs } from '../../lib/parse-duration';\n\nexport function useDrawerPresence(\n isOpen: boolean,\n backdropRef: RefObject<HTMLElement | null>,\n) {\n const [durationMs, setDurationMs] = useState(0);\n const [wasOpen, setWasOpen] = useState(isOpen);\n const [lingering, setLingering] = useState(false);\n\n if (wasOpen !== isOpen) {\n setWasOpen(isOpen);\n setLingering(!isOpen && durationMs > 0);\n }\n\n useEffect(() => {\n if (!isOpen || !backdropRef.current) return;\n setDurationMs(\n parseDurationMs(\n getComputedStyle(backdropRef.current).getPropertyValue(\n '--sf-motion-duration',\n ),\n ),\n );\n }, [isOpen, backdropRef]);\n\n useEffect(() => {\n if (!lingering) return;\n const timer = setTimeout(() => setLingering(false), durationMs);\n return () => clearTimeout(timer);\n }, [lingering, durationMs]);\n\n return { mounted: isOpen || lingering, state: isOpen ? 'open' : 'closed' };\n}\n","import { useCart } from '../../cart/cart-provider';\nimport { formatPrice } from '../../lib/format-price';\nimport { useStorefront } from '../../lib/provider';\n\nexport interface CartSummaryLabels {\n subtotal: string;\n}\n\nexport interface CartSummaryProps {\n onCheckout?: () => void;\n checkoutLabel?: string;\n checkoutDisabled?: boolean;\n labels?: Partial<CartSummaryLabels>;\n}\n\nexport function CartSummary({\n onCheckout,\n checkoutLabel = 'Checkout',\n checkoutDisabled = false,\n labels,\n}: CartSummaryProps) {\n const cart = useCart();\n const { locale } = useStorefront();\n const subtotal = labels?.subtotal ?? 'Subtotal';\n return (\n <div className=\"sf-cart-summary\">\n <p className=\"sf-cart-summary__row\">\n <span>{subtotal}</span>\n <span className=\"sf-price\">\n {formatPrice(cart.subtotalCents, cart.currency ?? 'USD', locale)}\n </span>\n </p>\n <button\n type=\"button\"\n className=\"sf-button\"\n disabled={checkoutDisabled || !onCheckout || cart.lines.length === 0}\n onClick={onCheckout}\n >\n {checkoutLabel}\n </button>\n </div>\n );\n}\n","import { useEffect, useRef, type KeyboardEvent, type ReactNode } from 'react';\nimport { useCart } from '../../cart/cart-provider';\nimport { CartLineItem } from '../cart-line-item/cart-line-item';\nimport { useDrawerPresence } from './use-drawer-presence';\nimport {\n CartSummary,\n type CartSummaryProps,\n} from '../cart-summary/cart-summary';\n\nexport interface CartDrawerLabels {\n title: string;\n close: string;\n empty: string;\n}\n\nexport const defaultCartDrawerLabels: CartDrawerLabels = {\n title: 'Your cart',\n close: 'Close',\n empty: 'Your cart is empty.',\n};\n\nexport interface CartDrawerProps {\n labels?: Partial<CartDrawerLabels>;\n summary?: CartSummaryProps;\n slots?: CartDrawerSlots;\n}\n\nexport interface CartDrawerSlots {\n empty?: ReactNode;\n summaryNote?: ReactNode;\n}\n\nconst FOCUSABLE =\n 'a[href], button:not([disabled]), input:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\nexport function CartDrawer({ labels, summary, slots }: CartDrawerProps) {\n const cart = useCart();\n const text = { ...defaultCartDrawerLabels, ...labels };\n const panelRef = useRef<HTMLDivElement>(null);\n const backdropRef = useRef<HTMLDivElement>(null);\n const { isOpen } = cart;\n const presence = useDrawerPresence(isOpen, backdropRef);\n\n useEffect(() => {\n if (!isOpen) return;\n const trigger = document.activeElement as HTMLElement | null;\n panelRef.current?.querySelector<HTMLElement>(FOCUSABLE)?.focus();\n return () => trigger?.focus();\n }, [isOpen]);\n\n if (!presence.mounted) return null;\n\n function handleKeyDown(event: KeyboardEvent<HTMLDivElement>) {\n if (event.key === 'Escape') {\n event.stopPropagation();\n cart.close();\n return;\n }\n if (event.key !== 'Tab') return;\n const items = Array.from(\n panelRef.current?.querySelectorAll<HTMLElement>(FOCUSABLE) ?? [],\n );\n if (items.length === 0) return;\n const first = items[0];\n const last = items[items.length - 1];\n if (event.shiftKey && document.activeElement === first) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && document.activeElement === last) {\n event.preventDefault();\n first.focus();\n }\n }\n\n return (\n <div\n ref={backdropRef}\n className=\"sf-drawer-backdrop\"\n data-state={presence.state}\n onClick={cart.close}\n >\n <div\n ref={panelRef}\n className=\"sf-drawer\"\n data-state={presence.state}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"sf-drawer-title\"\n onKeyDown={handleKeyDown}\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"sf-drawer__header\">\n <h2 id=\"sf-drawer-title\" className=\"sf-drawer__title\">\n {text.title}\n </h2>\n <button\n type=\"button\"\n className=\"sf-drawer__close\"\n aria-label={text.close}\n onClick={cart.close}\n >\n <span aria-hidden=\"true\">×</span>\n </button>\n </div>\n {cart.lines.length === 0 ? (\n <div className=\"sf-drawer__empty\">\n {slots?.empty ?? <p>{text.empty}</p>}\n </div>\n ) : (\n <>\n <ul className=\"sf-drawer__lines\">\n {cart.lines.map((line) => (\n <CartLineItem key={line.productId} line={line} />\n ))}\n </ul>\n <CartSummary {...summary} />\n {slots?.summaryNote && (\n <div className=\"sf-drawer__note\">{slots.summaryNote}</div>\n )}\n </>\n )}\n </div>\n </div>\n );\n}\n","import type { ReactNode } from 'react';\nimport { useCart } from '../../cart/cart-provider';\n\nexport interface CartButtonProps {\n label?: string;\n className?: string;\n icon?: ReactNode;\n labelHidden?: boolean;\n hideZeroCount?: boolean;\n}\n\nexport function CartButton({\n label = 'Cart',\n className,\n icon,\n labelHidden = false,\n hideZeroCount = false,\n}: CartButtonProps) {\n const cart = useCart();\n const count = cart.hydrated ? cart.itemCount : 0;\n const showCount = !(hideZeroCount && count === 0);\n const ariaLabel = showCount\n ? labelHidden\n ? `${label}, ${cart.itemCount} items`\n : undefined\n : `${label}, empty`;\n return (\n <button\n type=\"button\"\n className={['sf-cart-button', className].filter(Boolean).join(' ')}\n aria-haspopup=\"dialog\"\n aria-label={ariaLabel}\n onClick={cart.open}\n >\n {icon}\n <span className={labelHidden ? 'sf-visually-hidden' : undefined}>\n {label}\n </span>\n {showCount && (\n <span\n className=\"sf-cart-button__count\"\n aria-label={`${cart.itemCount} items`}\n >\n {count}\n </span>\n )}\n </button>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function formatPrice(cents: number, currency: string, locale?: string): string;
@@ -0,0 +1 @@
1
+ export declare function parseDurationMs(value: string): number;
@@ -0,0 +1,28 @@
1
+ import { ReactNode } from 'react';
2
+ export interface LinkProps {
3
+ href: string;
4
+ children: ReactNode;
5
+ className?: string;
6
+ }
7
+ export interface ImageProps {
8
+ src: string;
9
+ alt: string;
10
+ sizes?: string;
11
+ className?: string;
12
+ priority?: boolean;
13
+ }
14
+ export interface StorefrontConfig {
15
+ renderLink: (props: LinkProps) => ReactNode;
16
+ renderImage: (props: ImageProps) => ReactNode;
17
+ locale: string;
18
+ hrefs: {
19
+ product: (slug: string) => string;
20
+ category: (slug: string) => string;
21
+ };
22
+ }
23
+ export interface StorefrontProviderProps extends Partial<Omit<StorefrontConfig, 'hrefs'>> {
24
+ hrefs?: Partial<StorefrontConfig['hrefs']>;
25
+ children: ReactNode;
26
+ }
27
+ export declare function StorefrontProvider({ children, hrefs, ...overrides }: StorefrontProviderProps): import('react').JSX.Element;
28
+ export declare function useStorefront(): StorefrontConfig;
@@ -0,0 +1,15 @@
1
+ export interface StorefrontTheme {
2
+ colorBg?: string;
3
+ colorSurface?: string;
4
+ colorFg?: string;
5
+ colorMuted?: string;
6
+ colorAccent?: string;
7
+ colorAccentFg?: string;
8
+ colorBorder?: string;
9
+ colorSold?: string;
10
+ radius?: string;
11
+ fontDisplay?: string;
12
+ fontBody?: string;
13
+ spaceUnit?: string;
14
+ }
15
+ export declare function themeToCssVars(theme: StorefrontTheme): Record<string, string>;
@@ -0,0 +1,15 @@
1
+ // src/lib/format-price.ts
2
+ function e(e, t, n = "en-US") {
3
+ return new Intl.NumberFormat(n, {
4
+ style: "currency",
5
+ currency: t
6
+ }).format(e / 100);
7
+ }
8
+ // src/views/sort-for-display.ts
9
+ function t(e) {
10
+ let t = (e) => +(e.availability === "sold");
11
+ return [...e].sort((e, n) => t(e) - t(n));
12
+ }
13
+ export { e as n, t };
14
+
15
+ //# sourceMappingURL=sort-for-display-Cj6GMeCY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sort-for-display-Cj6GMeCY.js","names":["formatPrice","locale","cents","sortForDisplay","rank","products","a","b"],"sources":["../src/lib/format-price.ts","../src/views/sort-for-display.ts"],"sourcesContent":["export function formatPrice(\n cents: number,\n currency: string,\n locale = 'en-US',\n): string {\n return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(\n cents / 100,\n );\n}\n","import type { ProductCard } from '@develemit/commerce-contracts';\n\nexport function sortForDisplay(products: ProductCard[]): ProductCard[] {\n const rank = (p: ProductCard) => (p.availability === 'sold' ? 1 : 0);\n return [...products].sort((a, b) => rank(a) - rank(b));\n}\n"],"mappings":";AAAA,SAAgBA,EACd,GACA,GACA,IAAS,SACD;CACR,OAAO,IAAI,KAAK,aAAaC,GAAQ;EAAE,OAAO;EAAY;CAAS,CAAC,CAAC,CAAC,OACpEC,IAAQ,GACV;AACF;;ACNA,SAAgBC,EAAe,GAAwC;CACrE,IAAMC,KAAQ,MAAoB,IAAE,iBAAiB;CACrD,OAAO,CAAC,GAAGC,CAAQ,CAAC,CAAC,MAAM,GAAG,MAAMD,EAAKE,CAAC,IAAIF,EAAKG,CAAC,CAAC;AACvD"}
@@ -0,0 +1,476 @@
1
+ :root {
2
+ --sf-color-bg: #ffffff;
3
+ --sf-color-surface: #f6f6f6;
4
+ --sf-color-fg: #1a1a1a;
5
+ --sf-color-muted: #6b6b6b;
6
+ --sf-color-accent: #1a1a1a;
7
+ --sf-color-accent-fg: #ffffff;
8
+ --sf-color-border: #e0e0e0;
9
+ --sf-color-sold: #9a9a9a;
10
+ --sf-radius: 4px;
11
+ --sf-font-display: system-ui, sans-serif;
12
+ --sf-font-body: system-ui, sans-serif;
13
+ --sf-space-unit: 0.25rem;
14
+ --sf-motion-duration: 0ms;
15
+ --sf-color-focus: currentColor;
16
+ --sf-card-hover-lift: 0px;
17
+ --sf-card-hover-shadow: none;
18
+ --sf-card-image-hover-scale: 1;
19
+ --sf-sold-opacity: 0.55;
20
+ --sf-sold-filter: grayscale(0.6);
21
+ --sf-grid-columns-mobile: 1;
22
+ }
23
+ .sf-price {
24
+ font-family: var(--sf-font-body);
25
+ font-weight: 600;
26
+ color: var(--sf-color-fg);
27
+ }
28
+ .sf-price--sold {
29
+ color: var(--sf-color-sold);
30
+ text-decoration: line-through;
31
+ }
32
+
33
+ .sf-badge {
34
+ display: inline-block;
35
+ inline-size: fit-content;
36
+ padding: calc(var(--sf-space-unit) * 1) calc(var(--sf-space-unit) * 3);
37
+ border: 1px solid var(--sf-color-border);
38
+ border-radius: var(--sf-radius);
39
+ font-family: var(--sf-font-body);
40
+ font-size: 0.75rem;
41
+ letter-spacing: 0.04em;
42
+ text-transform: uppercase;
43
+ color: var(--sf-color-fg);
44
+ }
45
+ .sf-badge--unique {
46
+ border-color: var(--sf-color-accent);
47
+ }
48
+ .sf-badge--sold,
49
+ .sf-badge--reserved {
50
+ color: var(--sf-color-sold);
51
+ }
52
+
53
+ .sf-grid {
54
+ display: grid;
55
+ grid-template-columns: repeat(var(--sf-grid-columns-mobile), 1fr);
56
+ gap: calc(var(--sf-space-unit) * 6);
57
+ margin: 0;
58
+ padding: 0;
59
+ list-style: none;
60
+ }
61
+ @media (min-width: 40rem) {
62
+ .sf-grid {
63
+ grid-template-columns: repeat(2, 1fr);
64
+ }
65
+ }
66
+ @media (min-width: 64rem) {
67
+ .sf-grid {
68
+ grid-template-columns: repeat(3, 1fr);
69
+ }
70
+ }
71
+ .sf-grid-empty {
72
+ padding: calc(var(--sf-space-unit) * 12) 0;
73
+ text-align: center;
74
+ color: var(--sf-color-muted);
75
+ }
76
+
77
+ .sf-card {
78
+ background: var(--sf-color-surface);
79
+ border-radius: var(--sf-radius);
80
+ overflow: hidden;
81
+ transition:
82
+ transform var(--sf-motion-duration) ease,
83
+ box-shadow var(--sf-motion-duration) ease;
84
+ }
85
+ .sf-card:hover,
86
+ .sf-card:focus-within {
87
+ transform: translateY(calc(-1 * var(--sf-card-hover-lift)));
88
+ box-shadow: var(--sf-card-hover-shadow);
89
+ }
90
+ .sf-card:hover .sf-card__image,
91
+ .sf-card:focus-within .sf-card__image {
92
+ transform: scale(var(--sf-card-image-hover-scale));
93
+ }
94
+ .sf-card__link {
95
+ display: block;
96
+ color: inherit;
97
+ text-decoration: none;
98
+ }
99
+ .sf-card__media {
100
+ aspect-ratio: 4 / 5;
101
+ background: var(--sf-color-border);
102
+ }
103
+ .sf-card__image,
104
+ .sf-card__placeholder {
105
+ display: block;
106
+ width: 100%;
107
+ height: 100%;
108
+ object-fit: cover;
109
+ transition: transform var(--sf-motion-duration) ease;
110
+ }
111
+ .sf-card__body {
112
+ display: flex;
113
+ flex-direction: column;
114
+ align-items: flex-start;
115
+ gap: calc(var(--sf-space-unit) * 2);
116
+ padding: calc(var(--sf-space-unit) * 4);
117
+ }
118
+ .sf-card__title {
119
+ margin: 0;
120
+ font-family: var(--sf-font-display);
121
+ font-size: 1.125rem;
122
+ color: var(--sf-color-fg);
123
+ }
124
+ .sf-card--sold .sf-card__image {
125
+ opacity: var(--sf-sold-opacity);
126
+ filter: var(--sf-sold-filter);
127
+ }
128
+ .sf-card--sold .sf-card__title {
129
+ color: var(--sf-color-sold);
130
+ }
131
+
132
+ .sf-filter__list {
133
+ display: flex;
134
+ flex-wrap: wrap;
135
+ gap: calc(var(--sf-space-unit) * 2);
136
+ margin: 0;
137
+ padding: 0;
138
+ list-style: none;
139
+ }
140
+ .sf-filter__item {
141
+ display: inline-flex;
142
+ align-items: center;
143
+ min-height: 44px;
144
+ padding: 0 calc(var(--sf-space-unit) * 4);
145
+ border: 1px solid var(--sf-color-border);
146
+ border-radius: var(--sf-radius);
147
+ background: transparent;
148
+ font-family: var(--sf-font-body);
149
+ color: var(--sf-color-fg);
150
+ text-decoration: none;
151
+ cursor: pointer;
152
+ }
153
+ .sf-filter__item--active {
154
+ background: var(--sf-color-accent);
155
+ border-color: var(--sf-color-accent);
156
+ color: var(--sf-color-accent-fg);
157
+ }
158
+
159
+ .sf-gallery {
160
+ display: flex;
161
+ flex-direction: column;
162
+ gap: calc(var(--sf-space-unit) * 3);
163
+ }
164
+ .sf-gallery__stage {
165
+ display: flex;
166
+ overflow-x: auto;
167
+ scroll-snap-type: x mandatory;
168
+ }
169
+ .sf-gallery__slide {
170
+ position: relative;
171
+ flex: 0 0 100%;
172
+ scroll-snap-align: center;
173
+ aspect-ratio: 4 / 5;
174
+ }
175
+ .sf-gallery__slide[hidden] {
176
+ display: none;
177
+ }
178
+ .sf-gallery__image {
179
+ display: block;
180
+ width: 100%;
181
+ height: 100%;
182
+ object-fit: cover;
183
+ }
184
+ .sf-gallery__thumbs {
185
+ display: flex;
186
+ gap: calc(var(--sf-space-unit) * 2);
187
+ }
188
+ .sf-gallery__thumb {
189
+ min-width: 44px;
190
+ min-height: 44px;
191
+ position: relative;
192
+ width: 64px;
193
+ aspect-ratio: 1;
194
+ padding: 0;
195
+ border: 2px solid transparent;
196
+ border-radius: var(--sf-radius);
197
+ background: none;
198
+ overflow: hidden;
199
+ cursor: pointer;
200
+ }
201
+ .sf-gallery:focus-visible,
202
+ .sf-gallery__thumb:focus-visible {
203
+ outline: 2px solid var(--sf-color-focus);
204
+ outline-offset: 2px;
205
+ }
206
+ .sf-gallery__thumb--active {
207
+ border-color: var(--sf-color-accent);
208
+ }
209
+ .sf-gallery__thumb-image {
210
+ display: block;
211
+ width: 100%;
212
+ height: 100%;
213
+ object-fit: cover;
214
+ }
215
+
216
+ .sf-shop,
217
+ .sf-featured {
218
+ display: grid;
219
+ gap: calc(var(--sf-space-unit) * 3);
220
+ }
221
+
222
+ .sf-featured__heading {
223
+ font-family: var(--sf-font-display);
224
+ margin: 0;
225
+ }
226
+
227
+ .sf-detail {
228
+ display: grid;
229
+ gap: calc(var(--sf-space-unit) * 3);
230
+ }
231
+
232
+ .sf-detail__body {
233
+ display: grid;
234
+ gap: calc(var(--sf-space-unit) * 2);
235
+ align-content: start;
236
+ }
237
+
238
+ .sf-detail__title {
239
+ font-family: var(--sf-font-display);
240
+ margin: 0;
241
+ }
242
+
243
+ .sf-detail__specs {
244
+ display: grid;
245
+ grid-template-columns: max-content 1fr;
246
+ gap: var(--sf-space-unit) calc(var(--sf-space-unit) * 2);
247
+ margin: 0;
248
+ }
249
+
250
+ .sf-detail__specs dt {
251
+ color: var(--sf-color-muted);
252
+ }
253
+
254
+ .sf-detail__specs dd {
255
+ margin: 0;
256
+ }
257
+
258
+ @media (min-width: 768px) {
259
+ .sf-detail {
260
+ grid-template-columns: 1fr 1fr;
261
+ align-items: start;
262
+ }
263
+ }
264
+ .sf-button {
265
+ padding: calc(var(--sf-space-unit) * 3) calc(var(--sf-space-unit) * 6);
266
+ border: 1px solid var(--sf-color-accent);
267
+ border-radius: var(--sf-radius);
268
+ background: var(--sf-color-accent);
269
+ color: var(--sf-color-accent-fg);
270
+ font-family: var(--sf-font-body);
271
+ font-size: 1rem;
272
+ cursor: pointer;
273
+ }
274
+ .sf-button:disabled {
275
+ background: var(--sf-color-surface);
276
+ border-color: var(--sf-color-border);
277
+ color: var(--sf-color-sold);
278
+ cursor: not-allowed;
279
+ }
280
+
281
+ .sf-link-button {
282
+ padding: 0;
283
+ border: 0;
284
+ background: none;
285
+ color: var(--sf-color-muted);
286
+ font-family: var(--sf-font-body);
287
+ text-decoration: underline;
288
+ cursor: pointer;
289
+ }
290
+
291
+ .sf-cart-button {
292
+ display: inline-flex;
293
+ gap: calc(var(--sf-space-unit) * 2);
294
+ align-items: center;
295
+ padding: calc(var(--sf-space-unit) * 2) calc(var(--sf-space-unit) * 4);
296
+ border: 1px solid var(--sf-color-border);
297
+ border-radius: var(--sf-radius);
298
+ background: var(--sf-color-bg);
299
+ color: var(--sf-color-fg);
300
+ font-family: var(--sf-font-body);
301
+ cursor: pointer;
302
+ }
303
+ .sf-cart-button__count {
304
+ min-width: 1.25rem;
305
+ padding: 0 calc(var(--sf-space-unit) * 1);
306
+ border-radius: var(--sf-radius);
307
+ background: var(--sf-color-accent);
308
+ color: var(--sf-color-accent-fg);
309
+ font-size: 0.75rem;
310
+ text-align: center;
311
+ }
312
+
313
+ .sf-shop__pager {
314
+ display: flex;
315
+ justify-content: center;
316
+ }
317
+ .sf-shop__more {
318
+ display: inline-flex;
319
+ align-items: center;
320
+ justify-content: center;
321
+ min-height: 44px;
322
+ padding: 0 calc(var(--sf-space-unit) * 6);
323
+ border: 1px solid var(--sf-color-accent);
324
+ border-radius: var(--sf-radius);
325
+ background: var(--sf-color-accent);
326
+ color: var(--sf-color-accent-fg);
327
+ font-family: var(--sf-font-body);
328
+ text-decoration: none;
329
+ }
330
+ .sf-shop__more:focus-visible {
331
+ outline: 2px solid var(--sf-color-accent);
332
+ outline-offset: 2px;
333
+ }
334
+ .sf-drawer-backdrop {
335
+ position: fixed;
336
+ inset: 0;
337
+ z-index: 50;
338
+ display: flex;
339
+ justify-content: flex-end;
340
+ background: rgb(0 0 0 / 0.4);
341
+ opacity: 1;
342
+ transition: opacity var(--sf-motion-duration) ease;
343
+ animation: sf-fade-in var(--sf-motion-duration) ease;
344
+ }
345
+ .sf-drawer-backdrop[data-state='closed'] {
346
+ opacity: 0;
347
+ }
348
+ @keyframes sf-fade-in {
349
+ from {
350
+ opacity: 0;
351
+ }
352
+ }
353
+ @keyframes sf-slide-in {
354
+ from {
355
+ transform: translateX(100%);
356
+ }
357
+ }
358
+ @media (prefers-reduced-motion: reduce) {
359
+ .sf-drawer-backdrop {
360
+ --sf-motion-duration: 0ms;
361
+ }
362
+ }
363
+ .sf-drawer {
364
+ display: flex;
365
+ flex-direction: column;
366
+ gap: calc(var(--sf-space-unit) * 4);
367
+ width: min(24rem, 100%);
368
+ padding: calc(var(--sf-space-unit) * 6);
369
+ background: var(--sf-color-bg);
370
+ color: var(--sf-color-fg);
371
+ font-family: var(--sf-font-body);
372
+ overflow-y: auto;
373
+ transition: transform var(--sf-motion-duration) ease;
374
+ animation: sf-slide-in var(--sf-motion-duration) ease;
375
+ }
376
+ .sf-drawer[data-state='closed'] {
377
+ transform: translateX(100%);
378
+ }
379
+ .sf-drawer__header {
380
+ display: flex;
381
+ justify-content: space-between;
382
+ align-items: center;
383
+ }
384
+ .sf-drawer__title {
385
+ margin: 0;
386
+ font-family: var(--sf-font-display);
387
+ }
388
+ .sf-drawer__lines {
389
+ flex: 1;
390
+ margin: 0;
391
+ padding: 0;
392
+ list-style: none;
393
+ }
394
+ .sf-drawer__empty {
395
+ flex: 1;
396
+ color: var(--sf-color-muted);
397
+ }
398
+ .sf-drawer__empty > p {
399
+ margin: 0;
400
+ }
401
+ .sf-drawer__close,
402
+ .sf-cart-line__remove {
403
+ display: inline-flex;
404
+ align-items: center;
405
+ justify-content: center;
406
+ min-width: 44px;
407
+ min-height: 44px;
408
+ }
409
+ .sf-drawer__close {
410
+ padding: 0;
411
+ border: 0;
412
+ background: none;
413
+ color: var(--sf-color-fg);
414
+ font-size: 1.5rem;
415
+ line-height: 1;
416
+ cursor: pointer;
417
+ }
418
+ .sf-drawer__note {
419
+ color: var(--sf-color-muted);
420
+ font-size: 0.875rem;
421
+ }
422
+
423
+ .sf-visually-hidden {
424
+ position: absolute;
425
+ width: 1px;
426
+ height: 1px;
427
+ margin: -1px;
428
+ padding: 0;
429
+ border: 0;
430
+ overflow: hidden;
431
+ clip: rect(0 0 0 0);
432
+ white-space: nowrap;
433
+ }
434
+ .sf-cart-line {
435
+ display: flex;
436
+ gap: calc(var(--sf-space-unit) * 3);
437
+ padding: calc(var(--sf-space-unit) * 3) 0;
438
+ border-bottom: 1px solid var(--sf-color-border);
439
+ }
440
+ .sf-cart-line__image {
441
+ width: 5rem;
442
+ height: 5rem;
443
+ border-radius: var(--sf-radius);
444
+ object-fit: cover;
445
+ }
446
+ .sf-cart-line__body {
447
+ display: flex;
448
+ flex-direction: column;
449
+ gap: calc(var(--sf-space-unit) * 1);
450
+ align-items: flex-start;
451
+ }
452
+ .sf-cart-line__name {
453
+ color: var(--sf-color-fg);
454
+ font-weight: 600;
455
+ }
456
+ .sf-cart-line__qty input {
457
+ width: 3.5rem;
458
+ margin-left: calc(var(--sf-space-unit) * 2);
459
+ }
460
+
461
+ .sf-cart-summary {
462
+ display: flex;
463
+ flex-direction: column;
464
+ gap: calc(var(--sf-space-unit) * 3);
465
+ }
466
+ .sf-cart-summary__row {
467
+ display: flex;
468
+ justify-content: space-between;
469
+ margin: 0;
470
+ }
471
+ @media (prefers-reduced-motion: reduce) {
472
+ .sf-card,
473
+ .sf-card__image {
474
+ transition: none;
475
+ }
476
+ }