codexly-ui 0.12.24 → 0.12.25

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.
@@ -15366,9 +15366,11 @@ class ClxProductComponent {
15366
15366
  * variant="ghost" (text tinted at textSubtle strength, not a solid fill), so contrast risk is
15367
15367
  * low, but this is still exposed for full parity with clx-product-detail/clx-wishlist. */
15368
15368
  actionTextColor = input(undefined, ...(ngDevMode ? [{ debugName: "actionTextColor" }] : /* istanbul ignore next */ []));
15369
- /** Card drop shadow — same ClxCardShadow scale as clx-card ('none' turns it off entirely).
15370
- * Falls back to 'sm', the shadow this card has always rendered with. */
15371
- shadow = input(undefined, ...(ngDevMode ? [{ debugName: "shadow" }] : /* istanbul ignore next */ []));
15369
+ /** Same ClxCardVariant concept as clx-card: 'elevated' (border + shadow, the default this card
15370
+ * has always rendered with), 'outlined' (border, no shadow), 'flat' (neither). The border
15371
+ * itself always reads cardBorderColor() regardless of variant only whether it's shown at
15372
+ * all, and whether a shadow accompanies it, changes. */
15373
+ variant = input('elevated', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
15372
15374
  imageFit = input('cover', ...(ngDevMode ? [{ debugName: "imageFit" }] : /* istanbul ignore next */ []));
15373
15375
  addToCart = output();
15374
15376
  addToWishlist = output();
@@ -15392,10 +15394,12 @@ class ClxProductComponent {
15392
15394
  * so a host that pins clxColor to a one-off brand accent doesn't also drag the card border
15393
15395
  * along with it. */
15394
15396
  _cardClass = computed(() => {
15395
- const shadowCls = CARD_SHADOW_MAP[this.shadow() ?? 'sm'];
15397
+ const v = this.variant();
15396
15398
  const hover = this._disabled() ? '' : 'hover:shadow-md hover:-translate-y-0.5';
15397
15399
  const t = resolveColor(this._themeSvc.cardBorderColor());
15398
- return `bg-clx-surface rounded-2xl ${shadowCls} border ${t.borderLight} transition-[box-shadow,transform] duration-200 overflow-hidden group ${hover} ${this._h() ? 'flex flex-col sm:flex-row' : 'flex flex-col'}`;
15400
+ const borderCls = v === 'flat' ? 'border border-transparent' : `border ${t.borderLight}`;
15401
+ const shadowCls = v === 'elevated' ? 'shadow-sm' : '';
15402
+ return `bg-clx-surface rounded-2xl ${shadowCls} ${borderCls} transition-[box-shadow,transform] duration-200 overflow-hidden group ${hover} ${this._h() ? 'flex flex-col sm:flex-row' : 'flex flex-col'}`;
15399
15403
  }, ...(ngDevMode ? [{ debugName: "_cardClass" }] : /* istanbul ignore next */ []));
15400
15404
  _imageContainerClass = computed(() => this._h()
15401
15405
  ? `relative shrink-0 w-full aspect-square sm:aspect-auto ${this._sm() ? 'sm:w-32' : 'sm:w-44'} overflow-hidden rounded-t-2xl sm:rounded-t-none sm:rounded-l-2xl`
@@ -15427,7 +15431,7 @@ class ClxProductComponent {
15427
15431
  return `${sz} font-bold ${color}`;
15428
15432
  }, ...(ngDevMode ? [{ debugName: "_priceClass" }] : /* istanbul ignore next */ []));
15429
15433
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxProductComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
15430
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxProductComponent, isStandalone: true, selector: "clx-product", inputs: { product: { classPropertyName: "product", publicName: "product", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, priority: { classPropertyName: "priority", publicName: "priority", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, clxColor: { classPropertyName: "clxColor", publicName: "clxColor", isSignal: true, isRequired: false, transformFunction: null }, actionTextColor: { classPropertyName: "actionTextColor", publicName: "actionTextColor", isSignal: true, isRequired: false, transformFunction: null }, shadow: { classPropertyName: "shadow", publicName: "shadow", isSignal: true, isRequired: false, transformFunction: null }, imageFit: { classPropertyName: "imageFit", publicName: "imageFit", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { addToCart: "addToCart", addToWishlist: "addToWishlist", viewOptions: "viewOptions" }, ngImport: i0, template: `
15434
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxProductComponent, isStandalone: true, selector: "clx-product", inputs: { product: { classPropertyName: "product", publicName: "product", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, priority: { classPropertyName: "priority", publicName: "priority", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, clxColor: { classPropertyName: "clxColor", publicName: "clxColor", isSignal: true, isRequired: false, transformFunction: null }, actionTextColor: { classPropertyName: "actionTextColor", publicName: "actionTextColor", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, imageFit: { classPropertyName: "imageFit", publicName: "imageFit", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { addToCart: "addToCart", addToWishlist: "addToWishlist", viewOptions: "viewOptions" }, ngImport: i0, template: `
15431
15435
  @if (loading()) {
15432
15436
  @if (_h()) {
15433
15437
  <!-- Skeleton Horizontal -->
@@ -15697,7 +15701,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
15697
15701
  encapsulation: ViewEncapsulation.None,
15698
15702
  changeDetection: ChangeDetectionStrategy.OnPush,
15699
15703
  }]
15700
- }], propDecorators: { product: [{ type: i0.Input, args: [{ isSignal: true, alias: "product", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], priority: [{ type: i0.Input, args: [{ isSignal: true, alias: "priority", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], clxColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "clxColor", required: false }] }], actionTextColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionTextColor", required: false }] }], shadow: [{ type: i0.Input, args: [{ isSignal: true, alias: "shadow", required: false }] }], imageFit: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageFit", required: false }] }], addToCart: [{ type: i0.Output, args: ["addToCart"] }], addToWishlist: [{ type: i0.Output, args: ["addToWishlist"] }], viewOptions: [{ type: i0.Output, args: ["viewOptions"] }] } });
15704
+ }], propDecorators: { product: [{ type: i0.Input, args: [{ isSignal: true, alias: "product", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], priority: [{ type: i0.Input, args: [{ isSignal: true, alias: "priority", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], clxColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "clxColor", required: false }] }], actionTextColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionTextColor", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], imageFit: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageFit", required: false }] }], addToCart: [{ type: i0.Output, args: ["addToCart"] }], addToWishlist: [{ type: i0.Output, args: ["addToWishlist"] }], viewOptions: [{ type: i0.Output, args: ["viewOptions"] }] } });
15701
15705
 
15702
15706
  class ClxProductDetailComponent {
15703
15707
  product = input(null, ...(ngDevMode ? [{ debugName: "product" }] : /* istanbul ignore next */ []));
@@ -15764,10 +15768,11 @@ class ClxProductDetailComponent {
15764
15768
  /** Hides the tabs (Características/Especificaciones/Descripción) and the Reviews section —
15765
15769
  * for contexts like a quick-view modal where only the gallery + spec selector + CTA matter. */
15766
15770
  compact = input(false, ...(ngDevMode ? [{ debugName: "compact" }] : /* istanbul ignore next */ []));
15767
- /** Drop shadow for the gallery/info panels and each review card same ClxCardShadow scale as
15768
- * clx-card ('none' turns it off entirely). Falls back to 'sm', the shadow these have always
15769
- * rendered with. */
15770
- shadow = input(undefined, ...(ngDevMode ? [{ debugName: "shadow" }] : /* istanbul ignore next */ []));
15771
+ /** Same ClxCardVariant concept as clx-card: 'elevated' (border + shadow, the default the
15772
+ * gallery/info panels and each review card have always rendered with), 'outlined' (border, no
15773
+ * shadow), 'flat' (neither). The border itself always reads cardBorderColor() regardless of
15774
+ * variant. */
15775
+ variant = input('elevated', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
15771
15776
  addToCart = output();
15772
15777
  buyNow = output();
15773
15778
  wishlist = output();
@@ -15786,18 +15791,22 @@ class ClxProductDetailComponent {
15786
15791
  _initialVariation = signal(null, ...(ngDevMode ? [{ debugName: "_initialVariation" }] : /* istanbul ignore next */ []));
15787
15792
  _themeSvc = inject(ClxThemeService);
15788
15793
  /** Same card treatment as clx-product/clx-wishlist/clx-cart — themed border (cardBorderColor)
15789
- * + configurable shadow, keeps the gallery/info panels visually consistent with the rest of
15794
+ * + variant-driven shadow, keeps the gallery/info panels visually consistent with the rest of
15790
15795
  * the e-commerce card set. Was bg-clx-surface + shadow-sm with no border at all. */
15791
15796
  _panelClass = computed(() => {
15797
+ const v = this.variant();
15792
15798
  const t = resolveColor(this._themeSvc.cardBorderColor());
15793
- const shadowCls = CARD_SHADOW_MAP[this.shadow() ?? 'sm'];
15794
- return `bg-clx-surface rounded-2xl ${shadowCls} border ${t.borderLight} p-4`;
15799
+ const borderCls = v === 'flat' ? 'border border-transparent' : `border ${t.borderLight}`;
15800
+ const shadowCls = v === 'elevated' ? 'shadow-sm' : '';
15801
+ return `bg-clx-surface rounded-2xl ${shadowCls} ${borderCls} p-4`;
15795
15802
  }, ...(ngDevMode ? [{ debugName: "_panelClass" }] : /* istanbul ignore next */ []));
15796
15803
  /** Same border/shadow treatment for each individual review card. */
15797
15804
  _reviewCardClass = computed(() => {
15805
+ const v = this.variant();
15798
15806
  const t = resolveColor(this._themeSvc.cardBorderColor());
15799
- const shadowCls = CARD_SHADOW_MAP[this.shadow() ?? 'sm'];
15800
- return `rounded-2xl overflow-hidden ${shadowCls} border ${t.borderLight}`;
15807
+ const borderCls = v === 'flat' ? 'border border-transparent' : `border ${t.borderLight}`;
15808
+ const shadowCls = v === 'elevated' ? 'shadow-sm' : '';
15809
+ return `rounded-2xl overflow-hidden ${shadowCls} ${borderCls}`;
15801
15810
  }, ...(ngDevMode ? [{ debugName: "_reviewCardClass" }] : /* istanbul ignore next */ []));
15802
15811
  tabItems = [
15803
15812
  { id: 'general', label: 'Características' },
@@ -15977,7 +15986,7 @@ class ClxProductDetailComponent {
15977
15986
  specsLeft(specs) { return specs.slice(0, Math.ceil(specs.length / 2)); }
15978
15987
  specsRight(specs) { return specs.slice(Math.ceil(specs.length / 2)); }
15979
15988
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxProductDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
15980
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxProductDetailComponent, isStandalone: true, selector: "clx-product-detail", inputs: { product: { classPropertyName: "product", publicName: "product", isSignal: true, isRequired: false, transformFunction: null }, initialVariationId: { classPropertyName: "initialVariationId", publicName: "initialVariationId", isSignal: true, isRequired: false, transformFunction: null }, clxColor: { classPropertyName: "clxColor", publicName: "clxColor", isSignal: true, isRequired: false, transformFunction: null }, buyNowColor: { classPropertyName: "buyNowColor", publicName: "buyNowColor", isSignal: true, isRequired: false, transformFunction: null }, buyNowTextColor: { classPropertyName: "buyNowTextColor", publicName: "buyNowTextColor", isSignal: true, isRequired: false, transformFunction: null }, addToCartColor: { classPropertyName: "addToCartColor", publicName: "addToCartColor", isSignal: true, isRequired: false, transformFunction: null }, addToCartTextColor: { classPropertyName: "addToCartTextColor", publicName: "addToCartTextColor", isSignal: true, isRequired: false, transformFunction: null }, leaveReviewColor: { classPropertyName: "leaveReviewColor", publicName: "leaveReviewColor", isSignal: true, isRequired: false, transformFunction: null }, leaveReviewTextColor: { classPropertyName: "leaveReviewTextColor", publicName: "leaveReviewTextColor", isSignal: true, isRequired: false, transformFunction: null }, wishlistColor: { classPropertyName: "wishlistColor", publicName: "wishlistColor", isSignal: true, isRequired: false, transformFunction: null }, wishlistTextColor: { classPropertyName: "wishlistTextColor", publicName: "wishlistTextColor", isSignal: true, isRequired: false, transformFunction: null }, wishlistVariant: { classPropertyName: "wishlistVariant", publicName: "wishlistVariant", isSignal: true, isRequired: false, transformFunction: null }, stockBadgeColor: { classPropertyName: "stockBadgeColor", publicName: "stockBadgeColor", isSignal: true, isRequired: false, transformFunction: null }, discountBadgeColor: { classPropertyName: "discountBadgeColor", publicName: "discountBadgeColor", isSignal: true, isRequired: false, transformFunction: null }, quantityColor: { classPropertyName: "quantityColor", publicName: "quantityColor", isSignal: true, isRequired: false, transformFunction: null }, specButtonColor: { classPropertyName: "specButtonColor", publicName: "specButtonColor", isSignal: true, isRequired: false, transformFunction: null }, specButtonTextColor: { classPropertyName: "specButtonTextColor", publicName: "specButtonTextColor", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, currency: { classPropertyName: "currency", publicName: "currency", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, shadow: { classPropertyName: "shadow", publicName: "shadow", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { addToCart: "addToCart", buyNow: "buyNow", wishlist: "wishlist", variationChange: "variationChange", leaveReview: "leaveReview" }, viewQueries: [{ propertyName: "_carousel", first: true, predicate: ClxCarouselComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<!-- \u2550\u2550 SKELETON \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (loading()) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-8 p-6\">\n <div class=\"space-y-3\">\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"420px\" class=\"rounded-2xl\"></clx-skeleton>\n <div class=\"flex gap-2\">\n @for (t of [1,2,3,4]; track t) {\n <clx-skeleton variant=\"rectangular\" width=\"80px\" height=\"80px\" class=\"rounded-xl\"></clx-skeleton>\n }\n </div>\n </div>\n <div class=\"space-y-4 pt-2\">\n <clx-skeleton variant=\"text\" size=\"lg\" width=\"70%\"></clx-skeleton>\n <clx-skeleton variant=\"text\" size=\"sm\" width=\"40%\"></clx-skeleton>\n <clx-skeleton variant=\"text\" size=\"xl\" width=\"50%\"></clx-skeleton>\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"100px\" class=\"rounded-xl\"></clx-skeleton>\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"52px\" class=\"rounded-xl\"></clx-skeleton>\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"52px\" class=\"rounded-xl\"></clx-skeleton>\n </div>\n </div>\n}\n\n<!-- \u2550\u2550 CONTENT \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (!loading() && product(); as p) {\n @let v = activeVariation();\n <div class=\"space-y-8\">\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-8 md:items-start\">\n\n <!-- \u2500\u2500 Column 1: Image Gallery \u2014 each column sizes to its own content (no forced equal\n height, no position:sticky). A `sticky` gallery reads great in isolation, but a host app\n that already has its own `position: sticky` header (a full-width top bar, as opposed to\n this library's own `clx-app-layout` sidebar, which is `fixed` and never competes) forces\n the browser to keep recomputing both stickies' viewport-relative offsets together on every\n resize \u2014 confirmed as the source of a persistent resize-lag bug in codexly-store's product\n page. Not worth reintroducing until sticky positioning is made robust to an arbitrary host\n header. \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div [class]=\"_panelClass() + ' flex flex-col gap-3'\">\n <div class=\"relative aspect-4/3\">\n <div [class]=\"v && !v.in_stock ? 'opacity-40 grayscale' : ''\">\n <clx-carousel [clxColor]=\"clxColor()\" aspectRatio=\"4/3\" [loop]=\"true\">\n @for (img of v?.images ?? []; track i; let i = $index) {\n <ng-template clxSlide>\n <div class=\"w-full h-full p-5 sm:p-12.5 md:p-5 lg:p-12.5\">\n <img [src]=\"img\" [alt]=\"p.product_name\" class=\"w-full h-full object-contain\"\n [loading]=\"i === 0 ? 'eager' : 'lazy'\"\n [attr.fetchpriority]=\"i === 0 ? 'high' : null\" />\n </div>\n </ng-template>\n }\n </clx-carousel>\n </div>\n @if (v && !v.in_stock) {\n <div class=\"absolute inset-0 flex items-center justify-center pointer-events-none z-10\">\n <span clx-badge variant=\"solid\" color=\"slate\" size=\"md\" shape=\"pill\">Agotado</span>\n </div>\n }\n <div class=\"absolute top-3 right-3 z-10\">\n <button clx-button type=\"button\" [variant]=\"wishlistVariant()\" [color]=\"_wishlistColor()\" [textColor]=\"wishlistTextColor()\" shape=\"circle\" size=\"md\"\n [iconOnly]=\"true\" clxTooltip=\"Agregar a favoritos\" [clxTooltipColor]=\"_wishlistColor()\" clxTooltipPosition=\"left\"\n (click)=\"wishlist.emit()\">\n <span clx-icon name=\"favorite_border\" size=\"sm\"></span>\n </button>\n </div>\n </div>\n <div class=\"flex flex-wrap gap-2\">\n @for (img of v?.images ?? []; track i; let i = $index) {\n <img [src]=\"img\" [alt]=\"p.product_name\"\n class=\"w-20 h-20 rounded-xl object-cover border-[3px] cursor-pointer shrink-0 transition-[border-color,opacity]\"\n [class]=\"_carousel()?.currentPageIndex() === i\n ? _thumbBorderClass() + ' opacity-100'\n : 'border-transparent opacity-60 hover:opacity-90'\"\n loading=\"lazy\"\n (click)=\"_carousel()?.goTo(i)\" />\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Column 2: Purchase Info \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div [class]=\"_panelClass() + ' space-y-2.5'\">\n\n <!-- Stock status -->\n @if (v) {\n <span clx-badge variant=\"solid\" [color]=\"v.in_stock ? _stockBadgeColor() : 'red'\" size=\"sm\" shape=\"pill\">\n @if (v.in_stock) {\n @if (v.stock_quantity != null) {\n {{ v.stock_quantity }} en stock\n } @else {\n En stock\n }\n } @else {\n Sin stock\n }\n </span>\n }\n\n <!-- Tags (st_products.tags) -->\n <div class=\"flex flex-wrap gap-1\">\n @for (tag of p.tags; track tag) {\n <span clx-badge [variant]=\"tagVariant(tag)\" [color]=\"tagColor(tag)\" size=\"sm\" shape=\"pill\">\n {{ tagLabel(tag) }}\n </span>\n }\n </div>\n\n <!-- Title -->\n <div>\n <p class=\"text-sm text-clx-text-subtle uppercase tracking-wide font-medium\">{{ p.trademark }}</p>\n <h1 class=\"text-2xl font-bold text-clx-text-label leading-tight mt-0.5\">{{ v?.name || p.product_name }}</h1>\n @if (p.product_detail) {\n <p class=\"text-clx-text-muted text-sm mt-1\">{{ p.product_detail }}</p>\n }\n </div>\n\n <!-- Rating -->\n <div class=\"flex items-center gap-2\">\n <clx-rating [value]=\"p.rating\" [color]=\"clxColor()\"></clx-rating>\n <span class=\"text-sm font-semibold text-clx-text-label\">{{ p.rating | number:'1.1-1' }}</span>\n <span class=\"text-sm text-clx-text-subtle\">({{ p.review_count }} reviews)</span>\n </div>\n\n <!-- Price block -->\n <div>\n <div class=\"flex items-baseline gap-3\">\n <span class=\"text-2xl font-black text-clx-text-label\">\n {{ finalPrice() | currency:currency():'$':'1.0-0' }}\n </span>\n @if (p.has_discount && p.discount_price) {\n <span class=\"text-base text-clx-text-subtle line-through\">\n {{ p.discount_price | currency:currency():'$':'1.0-0' }}\n </span>\n <span clx-badge variant=\"solid\" [color]=\"_discountBadgeColor()\" size=\"sm\" shape=\"pill\">\n -{{ discount() }}%\n </span>\n }\n </div>\n </div>\n\n <!-- Selectores de variaci\u00F3n (st_spec_types + st_spec_options) \u2014 opciones no disponibles\n para la combinaci\u00F3n actual (ej. Talla S con Color Rojo) se ocultan por completo en vez\n de mostrarse deshabilitadas; reaparecen si el usuario cambia a una combinaci\u00F3n donde s\u00ED\n aplican, ya que isOptionAvailable() se reeval\u00FAa contra selectedSpecs() en cada cambio. -->\n @for (specType of p.spec_types; track specType.id) {\n @if (specType.input_type === 'color') {\n <clx-button-group [label]=\"specType.name\" shape=\"circular\" [attached]=\"false\">\n @for (opt of specType.options; track opt.id) {\n @if (isOptionAvailable(specType.id, opt.id)) {\n <button\n type=\"button\"\n class=\"w-7 h-7 rounded-full border-2 transition-[transform,box-shadow] shrink-0 cursor-pointer\"\n [class]=\"isSpecSelected(specType.id, opt.id) ? 'scale-110 shadow-md' : 'opacity-90 hover:opacity-100'\"\n [style.background-color]=\"opt.value\"\n [style.border-color]=\"isSpecSelected(specType.id, opt.id) ? 'var(--clx-text-label, #111)' : 'transparent'\"\n [clxTooltip]=\"opt.label ?? opt.value\"\n clxTooltipPosition=\"top\"\n (click)=\"selectSpec(specType.id, opt.id)\">\n </button>\n }\n }\n </clx-button-group>\n } @else {\n <clx-button-group [label]=\"specType.name\" shape=\"flat\" [attached]=\"false\">\n @for (opt of specType.options; track opt.id) {\n @if (isOptionAvailable(specType.id, opt.id)) {\n <button clx-button\n type=\"button\"\n [variant]=\"isSpecSelected(specType.id, opt.id) ? 'solid' : 'outline'\"\n [color]=\"_specButtonColor()\"\n [textColor]=\"isSpecSelected(specType.id, opt.id) ? specButtonTextColor() : undefined\"\n size=\"sm\"\n (click)=\"selectSpec(specType.id, opt.id)\">\n {{ opt.label ?? opt.value }}\n </button>\n }\n }\n </clx-button-group>\n }\n }\n\n <!-- Quantity -->\n <clx-number label=\"Cantidad:\" variant=\"stepper\" [color]=\"_quantityColor()\"\n [min]=\"1\" [max]=\"99\" [ngModel]=\"quantity()\" (ngModelChange)=\"quantity.set($event)\">\n </clx-number>\n\n <!-- CTA -->\n @if (v?.in_stock && !isSelectionComplete()) {\n <p class=\"text-sm text-red-500\">Selecciona {{ missingSpecNames().join(' y ') }} para continuar.</p>\n }\n <div class=\"flex gap-3\">\n <button clx-button type=\"button\" variant=\"solid\" [color]=\"_buyNowColor()\" [textColor]=\"buyNowTextColor()\" size=\"md\" [block]=\"true\" icon=\"bolt\"\n [disabled]=\"!v?.in_stock || !isSelectionComplete()\" (click)=\"_emitBuyNow()\">Comprar ahora</button>\n <button clx-button type=\"button\" variant=\"light\" emphasis=\"secondary\" [color]=\"addToCartColor()\" [textColor]=\"addToCartTextColor()\" size=\"md\" [block]=\"true\" icon=\"shopping_cart\"\n [disabled]=\"!v?.in_stock || !isSelectionComplete()\" (click)=\"_emitAddToCart()\">Agrega al carrito</button>\n </div>\n\n </div>\n </div>\n\n @if (!compact()) {\n <!-- \u2500\u2500 Tabs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <clx-tabs [tabs]=\"tabItems\" [clxColor]=\"clxColor()\" [(activeTab)]=\"activeTab\">\n\n <!-- Tab: Caracter\u00EDsticas generales (st_product_specs \u2014 ficha t\u00E9cnica global) -->\n <ng-template [clxTabPanel]=\"'general'\">\n <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-x-8\">\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (spec of specsLeft(p.product_specs); track spec.id) {\n <clx-list-item [icon]=\"spec.icon ?? ''\">\n <div clxContent>\n <p class=\"text-sm font-medium text-clx-text-label\">{{ spec.name }}:</p>\n <p class=\"text-sm text-clx-text-muted mt-0.5\">{{ spec.value }}</p>\n </div>\n </clx-list-item>\n }\n </clx-list>\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (spec of specsRight(p.product_specs); track spec.id) {\n <clx-list-item [icon]=\"spec.icon ?? ''\">\n <div clxContent>\n <p class=\"text-sm font-medium text-clx-text-label\">{{ spec.name }}:</p>\n <p class=\"text-sm text-clx-text-muted mt-0.5\">{{ spec.value }}</p>\n </div>\n </clx-list-item>\n }\n </clx-list>\n </div>\n </ng-template>\n\n <!-- Tab: Especificaciones (st_spec_types \u2014 specs de la variaci\u00F3n activa) -->\n <ng-template [clxTabPanel]=\"'specs'\">\n <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-x-8\">\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (st of specsLeft(p.spec_types); track st.id) {\n <clx-list-item [label]=\"st.name + ':'\" [sublabel]=\"selectedOptionLabel(st)\" [icon]=\"st.icon ?? ''\" />\n }\n </clx-list>\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (st of specsRight(p.spec_types); track st.id) {\n <clx-list-item [label]=\"st.name + ':'\" [sublabel]=\"selectedOptionLabel(st)\" [icon]=\"st.icon ?? ''\" />\n }\n </clx-list>\n </div>\n </ng-template>\n\n <!-- Tab: Descripci\u00F3n (st_product_descriptions) -->\n <ng-template [clxTabPanel]=\"'description'\">\n <div class=\"space-y-5\">\n @for (block of p.descriptions; track block.id) {\n <div>\n @if (block.title) {\n <h3 class=\"text-base font-semibold text-clx-text-label mb-1\">{{ block.title }}</h3>\n }\n <p class=\"text-sm text-clx-text-muted leading-relaxed\">{{ block.body }}</p>\n </div>\n }\n </div>\n </ng-template>\n\n </clx-tabs>\n\n <!-- \u2500\u2500 Reviews \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"grid grid-cols-1 xl:grid-cols-3 gap-8 border-t border-clx-border pt-8\">\n\n <div class=\"space-y-3\">\n <h2 class=\"text-base font-bold text-clx-text-label\">Opiniones de clientes</h2>\n <div class=\"flex items-end gap-3\">\n <span class=\"text-5xl font-black text-clx-text-label\">{{ p.rating | number:'1.1-1' }}</span>\n <div class=\"pb-1 space-y-1\">\n <clx-rating [value]=\"p.rating\" [color]=\"clxColor()\"></clx-rating>\n <p class=\"text-xs text-clx-text-muted\">{{ p.review_count }} valoraciones verificadas</p>\n </div>\n </div>\n <p class=\"text-sm font-semibold\" [class]=\"_ratingLabelClass()\">{{ _ratingLabel() }}</p>\n <div class=\"pt-2 space-y-2\">\n <button clx-button type=\"button\" variant=\"solid\" [color]=\"_leaveReviewColor()\" [textColor]=\"leaveReviewTextColor()\" size=\"md\" [block]=\"true\" icon=\"rate_review\"\n (click)=\"leaveReview.emit()\">\n Dejar una rese\u00F1a\n </button>\n </div>\n </div>\n\n <div class=\"lg:col-span-2 space-y-4\">\n @for (review of p.reviews ?? []; track review.id) {\n <div [class]=\"_reviewCardClass()\">\n <div class=\"flex items-center gap-3 px-4 py-3\">\n <div class=\"w-10 h-10 rounded-full bg-clx-surface-2 flex items-center justify-center shrink-0\">\n <span class=\"text-sm font-bold text-clx-text-muted\">\n {{ review.author_name.substring(0,1) }}{{ review.author_name.split(' ')[1]?.substring(0,1) ?? '' }}\n </span>\n </div>\n <div class=\"flex-1 min-w-0\">\n <p class=\"font-semibold text-sm text-clx-text-label truncate\">{{ review.author_name }}</p>\n <p class=\"text-xs text-clx-text-subtle\">{{ review.created_at }}</p>\n </div>\n @if (review.is_verified) {\n <span clx-badge variant=\"light\" color=\"emerald\" size=\"sm\" shape=\"pill\">Verified</span>\n }\n </div>\n <div class=\"px-4 pb-4 space-y-2\">\n <div class=\"flex items-center gap-2\">\n <clx-rating [value]=\"review.rating\" [color]=\"clxColor()\"></clx-rating>\n @if (review.title) {\n <span class=\"text-sm font-semibold text-clx-text-label\">{{ review.title }}</span>\n }\n </div>\n @if (review.body) {\n <p class=\"text-sm text-clx-text-muted\">{{ review.body }}</p>\n }\n <div class=\"flex items-center gap-3 pt-1\">\n <span class=\"text-xs text-clx-text-subtle\">\u00BFTe result\u00F3 \u00FAtil?</span>\n <button clx-button type=\"button\" variant=\"ghost\" color=\"slate\" size=\"xs\" icon=\"thumb_up\">{{ review.helpful }}</button>\n <button clx-button type=\"button\" variant=\"ghost\" color=\"slate\" size=\"xs\" icon=\"thumb_down\">{{ review.not_helpful }}</button>\n </div>\n </div>\n </div>\n }\n </div>\n\n </div>\n }\n\n </div>\n}\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ClxButtonComponent, selector: "button[clx-button], a[clx-button]", inputs: ["variant", "color", "emphasis", "textColor", "size", "shape", "loading", "disabled", "block", "icon", "iconPosition", "iconOnly", "badge", "badgeColor"] }, { kind: "component", type: ClxButtonGroupComponent, selector: "clx-button-group", inputs: ["label", "shape", "attached", "orientation"] }, { kind: "component", type: ClxBadgeComponent, selector: "span[clx-badge]", inputs: ["variant", "color", "size", "shape", "positioned"] }, { kind: "component", type: ClxIconComponent, selector: "span[clx-icon]", inputs: ["name", "size", "color", "fill"] }, { kind: "component", type: ClxSkeletonComponent, selector: "clx-skeleton", inputs: ["variant", "size", "width", "height"] }, { kind: "component", type: ClxRatingComponent, selector: "clx-rating", inputs: ["value", "max", "color"] }, { kind: "component", type: ClxCarouselComponent, selector: "clx-carousel", inputs: ["clxColor", "autoPlay", "interval", "loop", "aspectRatio", "transparent"] }, { kind: "directive", type: ClxCarouselDirective, selector: "[clxSlide]" }, { kind: "component", type: ClxTabsComponent, selector: "clx-tabs", inputs: ["tabs", "clxColor", "activeTextColor", "underlineColor", "activeTab"], outputs: ["activeTabChange"] }, { kind: "directive", type: ClxTabDirective, selector: "[clxTabPanel]", inputs: ["clxTabPanel"] }, { kind: "component", type: ClxNumberComponent, selector: "clx-number", inputs: ["variant", "size", "color", "label", "placeholder", "hint", "prefixIcon", "min", "max", "step", "value", "disabled"] }, { kind: "directive", type: ClxTooltipDirective, selector: "[clxTooltip]", inputs: ["clxTooltip", "clxTooltipPosition", "clxTooltipColor", "clxTooltipSize", "clxTooltipDelay"] }, { kind: "component", type: ClxListComponent, selector: "clx-list", inputs: ["color", "variant", "size", "numbered"] }, { kind: "component", type: ClxListItemComponent, selector: "clx-list-item", inputs: ["label", "sublabel", "wrapSublabel", "icon", "meta", "metaColor", "disabled"] }, { kind: "pipe", type: CurrencyPipe, name: "currency" }, { kind: "pipe", type: DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
15989
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxProductDetailComponent, isStandalone: true, selector: "clx-product-detail", inputs: { product: { classPropertyName: "product", publicName: "product", isSignal: true, isRequired: false, transformFunction: null }, initialVariationId: { classPropertyName: "initialVariationId", publicName: "initialVariationId", isSignal: true, isRequired: false, transformFunction: null }, clxColor: { classPropertyName: "clxColor", publicName: "clxColor", isSignal: true, isRequired: false, transformFunction: null }, buyNowColor: { classPropertyName: "buyNowColor", publicName: "buyNowColor", isSignal: true, isRequired: false, transformFunction: null }, buyNowTextColor: { classPropertyName: "buyNowTextColor", publicName: "buyNowTextColor", isSignal: true, isRequired: false, transformFunction: null }, addToCartColor: { classPropertyName: "addToCartColor", publicName: "addToCartColor", isSignal: true, isRequired: false, transformFunction: null }, addToCartTextColor: { classPropertyName: "addToCartTextColor", publicName: "addToCartTextColor", isSignal: true, isRequired: false, transformFunction: null }, leaveReviewColor: { classPropertyName: "leaveReviewColor", publicName: "leaveReviewColor", isSignal: true, isRequired: false, transformFunction: null }, leaveReviewTextColor: { classPropertyName: "leaveReviewTextColor", publicName: "leaveReviewTextColor", isSignal: true, isRequired: false, transformFunction: null }, wishlistColor: { classPropertyName: "wishlistColor", publicName: "wishlistColor", isSignal: true, isRequired: false, transformFunction: null }, wishlistTextColor: { classPropertyName: "wishlistTextColor", publicName: "wishlistTextColor", isSignal: true, isRequired: false, transformFunction: null }, wishlistVariant: { classPropertyName: "wishlistVariant", publicName: "wishlistVariant", isSignal: true, isRequired: false, transformFunction: null }, stockBadgeColor: { classPropertyName: "stockBadgeColor", publicName: "stockBadgeColor", isSignal: true, isRequired: false, transformFunction: null }, discountBadgeColor: { classPropertyName: "discountBadgeColor", publicName: "discountBadgeColor", isSignal: true, isRequired: false, transformFunction: null }, quantityColor: { classPropertyName: "quantityColor", publicName: "quantityColor", isSignal: true, isRequired: false, transformFunction: null }, specButtonColor: { classPropertyName: "specButtonColor", publicName: "specButtonColor", isSignal: true, isRequired: false, transformFunction: null }, specButtonTextColor: { classPropertyName: "specButtonTextColor", publicName: "specButtonTextColor", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, currency: { classPropertyName: "currency", publicName: "currency", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { addToCart: "addToCart", buyNow: "buyNow", wishlist: "wishlist", variationChange: "variationChange", leaveReview: "leaveReview" }, viewQueries: [{ propertyName: "_carousel", first: true, predicate: ClxCarouselComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<!-- \u2550\u2550 SKELETON \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (loading()) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-8 p-6\">\n <div class=\"space-y-3\">\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"420px\" class=\"rounded-2xl\"></clx-skeleton>\n <div class=\"flex gap-2\">\n @for (t of [1,2,3,4]; track t) {\n <clx-skeleton variant=\"rectangular\" width=\"80px\" height=\"80px\" class=\"rounded-xl\"></clx-skeleton>\n }\n </div>\n </div>\n <div class=\"space-y-4 pt-2\">\n <clx-skeleton variant=\"text\" size=\"lg\" width=\"70%\"></clx-skeleton>\n <clx-skeleton variant=\"text\" size=\"sm\" width=\"40%\"></clx-skeleton>\n <clx-skeleton variant=\"text\" size=\"xl\" width=\"50%\"></clx-skeleton>\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"100px\" class=\"rounded-xl\"></clx-skeleton>\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"52px\" class=\"rounded-xl\"></clx-skeleton>\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"52px\" class=\"rounded-xl\"></clx-skeleton>\n </div>\n </div>\n}\n\n<!-- \u2550\u2550 CONTENT \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (!loading() && product(); as p) {\n @let v = activeVariation();\n <div class=\"space-y-8\">\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-8 md:items-start\">\n\n <!-- \u2500\u2500 Column 1: Image Gallery \u2014 each column sizes to its own content (no forced equal\n height, no position:sticky). A `sticky` gallery reads great in isolation, but a host app\n that already has its own `position: sticky` header (a full-width top bar, as opposed to\n this library's own `clx-app-layout` sidebar, which is `fixed` and never competes) forces\n the browser to keep recomputing both stickies' viewport-relative offsets together on every\n resize \u2014 confirmed as the source of a persistent resize-lag bug in codexly-store's product\n page. Not worth reintroducing until sticky positioning is made robust to an arbitrary host\n header. \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div [class]=\"_panelClass() + ' flex flex-col gap-3'\">\n <div class=\"relative aspect-4/3\">\n <div [class]=\"v && !v.in_stock ? 'opacity-40 grayscale' : ''\">\n <clx-carousel [clxColor]=\"clxColor()\" aspectRatio=\"4/3\" [loop]=\"true\">\n @for (img of v?.images ?? []; track i; let i = $index) {\n <ng-template clxSlide>\n <div class=\"w-full h-full p-5 sm:p-12.5 md:p-5 lg:p-12.5\">\n <img [src]=\"img\" [alt]=\"p.product_name\" class=\"w-full h-full object-contain\"\n [loading]=\"i === 0 ? 'eager' : 'lazy'\"\n [attr.fetchpriority]=\"i === 0 ? 'high' : null\" />\n </div>\n </ng-template>\n }\n </clx-carousel>\n </div>\n @if (v && !v.in_stock) {\n <div class=\"absolute inset-0 flex items-center justify-center pointer-events-none z-10\">\n <span clx-badge variant=\"solid\" color=\"slate\" size=\"md\" shape=\"pill\">Agotado</span>\n </div>\n }\n <div class=\"absolute top-3 right-3 z-10\">\n <button clx-button type=\"button\" [variant]=\"wishlistVariant()\" [color]=\"_wishlistColor()\" [textColor]=\"wishlistTextColor()\" shape=\"circle\" size=\"md\"\n [iconOnly]=\"true\" clxTooltip=\"Agregar a favoritos\" [clxTooltipColor]=\"_wishlistColor()\" clxTooltipPosition=\"left\"\n (click)=\"wishlist.emit()\">\n <span clx-icon name=\"favorite_border\" size=\"sm\"></span>\n </button>\n </div>\n </div>\n <div class=\"flex flex-wrap gap-2\">\n @for (img of v?.images ?? []; track i; let i = $index) {\n <img [src]=\"img\" [alt]=\"p.product_name\"\n class=\"w-20 h-20 rounded-xl object-cover border-[3px] cursor-pointer shrink-0 transition-[border-color,opacity]\"\n [class]=\"_carousel()?.currentPageIndex() === i\n ? _thumbBorderClass() + ' opacity-100'\n : 'border-transparent opacity-60 hover:opacity-90'\"\n loading=\"lazy\"\n (click)=\"_carousel()?.goTo(i)\" />\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Column 2: Purchase Info \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div [class]=\"_panelClass() + ' space-y-2.5'\">\n\n <!-- Stock status -->\n @if (v) {\n <span clx-badge variant=\"solid\" [color]=\"v.in_stock ? _stockBadgeColor() : 'red'\" size=\"sm\" shape=\"pill\">\n @if (v.in_stock) {\n @if (v.stock_quantity != null) {\n {{ v.stock_quantity }} en stock\n } @else {\n En stock\n }\n } @else {\n Sin stock\n }\n </span>\n }\n\n <!-- Tags (st_products.tags) -->\n <div class=\"flex flex-wrap gap-1\">\n @for (tag of p.tags; track tag) {\n <span clx-badge [variant]=\"tagVariant(tag)\" [color]=\"tagColor(tag)\" size=\"sm\" shape=\"pill\">\n {{ tagLabel(tag) }}\n </span>\n }\n </div>\n\n <!-- Title -->\n <div>\n <p class=\"text-sm text-clx-text-subtle uppercase tracking-wide font-medium\">{{ p.trademark }}</p>\n <h1 class=\"text-2xl font-bold text-clx-text-label leading-tight mt-0.5\">{{ v?.name || p.product_name }}</h1>\n @if (p.product_detail) {\n <p class=\"text-clx-text-muted text-sm mt-1\">{{ p.product_detail }}</p>\n }\n </div>\n\n <!-- Rating -->\n <div class=\"flex items-center gap-2\">\n <clx-rating [value]=\"p.rating\" [color]=\"clxColor()\"></clx-rating>\n <span class=\"text-sm font-semibold text-clx-text-label\">{{ p.rating | number:'1.1-1' }}</span>\n <span class=\"text-sm text-clx-text-subtle\">({{ p.review_count }} reviews)</span>\n </div>\n\n <!-- Price block -->\n <div>\n <div class=\"flex items-baseline gap-3\">\n <span class=\"text-2xl font-black text-clx-text-label\">\n {{ finalPrice() | currency:currency():'$':'1.0-0' }}\n </span>\n @if (p.has_discount && p.discount_price) {\n <span class=\"text-base text-clx-text-subtle line-through\">\n {{ p.discount_price | currency:currency():'$':'1.0-0' }}\n </span>\n <span clx-badge variant=\"solid\" [color]=\"_discountBadgeColor()\" size=\"sm\" shape=\"pill\">\n -{{ discount() }}%\n </span>\n }\n </div>\n </div>\n\n <!-- Selectores de variaci\u00F3n (st_spec_types + st_spec_options) \u2014 opciones no disponibles\n para la combinaci\u00F3n actual (ej. Talla S con Color Rojo) se ocultan por completo en vez\n de mostrarse deshabilitadas; reaparecen si el usuario cambia a una combinaci\u00F3n donde s\u00ED\n aplican, ya que isOptionAvailable() se reeval\u00FAa contra selectedSpecs() en cada cambio. -->\n @for (specType of p.spec_types; track specType.id) {\n @if (specType.input_type === 'color') {\n <clx-button-group [label]=\"specType.name\" shape=\"circular\" [attached]=\"false\">\n @for (opt of specType.options; track opt.id) {\n @if (isOptionAvailable(specType.id, opt.id)) {\n <button\n type=\"button\"\n class=\"w-7 h-7 rounded-full border-2 transition-[transform,box-shadow] shrink-0 cursor-pointer\"\n [class]=\"isSpecSelected(specType.id, opt.id) ? 'scale-110 shadow-md' : 'opacity-90 hover:opacity-100'\"\n [style.background-color]=\"opt.value\"\n [style.border-color]=\"isSpecSelected(specType.id, opt.id) ? 'var(--clx-text-label, #111)' : 'transparent'\"\n [clxTooltip]=\"opt.label ?? opt.value\"\n clxTooltipPosition=\"top\"\n (click)=\"selectSpec(specType.id, opt.id)\">\n </button>\n }\n }\n </clx-button-group>\n } @else {\n <clx-button-group [label]=\"specType.name\" shape=\"flat\" [attached]=\"false\">\n @for (opt of specType.options; track opt.id) {\n @if (isOptionAvailable(specType.id, opt.id)) {\n <button clx-button\n type=\"button\"\n [variant]=\"isSpecSelected(specType.id, opt.id) ? 'solid' : 'outline'\"\n [color]=\"_specButtonColor()\"\n [textColor]=\"isSpecSelected(specType.id, opt.id) ? specButtonTextColor() : undefined\"\n size=\"sm\"\n (click)=\"selectSpec(specType.id, opt.id)\">\n {{ opt.label ?? opt.value }}\n </button>\n }\n }\n </clx-button-group>\n }\n }\n\n <!-- Quantity -->\n <clx-number label=\"Cantidad:\" variant=\"stepper\" [color]=\"_quantityColor()\"\n [min]=\"1\" [max]=\"99\" [ngModel]=\"quantity()\" (ngModelChange)=\"quantity.set($event)\">\n </clx-number>\n\n <!-- CTA -->\n @if (v?.in_stock && !isSelectionComplete()) {\n <p class=\"text-sm text-red-500\">Selecciona {{ missingSpecNames().join(' y ') }} para continuar.</p>\n }\n <div class=\"flex gap-3\">\n <button clx-button type=\"button\" variant=\"solid\" [color]=\"_buyNowColor()\" [textColor]=\"buyNowTextColor()\" size=\"md\" [block]=\"true\" icon=\"bolt\"\n [disabled]=\"!v?.in_stock || !isSelectionComplete()\" (click)=\"_emitBuyNow()\">Comprar ahora</button>\n <button clx-button type=\"button\" variant=\"light\" emphasis=\"secondary\" [color]=\"addToCartColor()\" [textColor]=\"addToCartTextColor()\" size=\"md\" [block]=\"true\" icon=\"shopping_cart\"\n [disabled]=\"!v?.in_stock || !isSelectionComplete()\" (click)=\"_emitAddToCart()\">Agrega al carrito</button>\n </div>\n\n </div>\n </div>\n\n @if (!compact()) {\n <!-- \u2500\u2500 Tabs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <clx-tabs [tabs]=\"tabItems\" [clxColor]=\"clxColor()\" [(activeTab)]=\"activeTab\">\n\n <!-- Tab: Caracter\u00EDsticas generales (st_product_specs \u2014 ficha t\u00E9cnica global) -->\n <ng-template [clxTabPanel]=\"'general'\">\n <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-x-8\">\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (spec of specsLeft(p.product_specs); track spec.id) {\n <clx-list-item [icon]=\"spec.icon ?? ''\">\n <div clxContent>\n <p class=\"text-sm font-medium text-clx-text-label\">{{ spec.name }}:</p>\n <p class=\"text-sm text-clx-text-muted mt-0.5\">{{ spec.value }}</p>\n </div>\n </clx-list-item>\n }\n </clx-list>\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (spec of specsRight(p.product_specs); track spec.id) {\n <clx-list-item [icon]=\"spec.icon ?? ''\">\n <div clxContent>\n <p class=\"text-sm font-medium text-clx-text-label\">{{ spec.name }}:</p>\n <p class=\"text-sm text-clx-text-muted mt-0.5\">{{ spec.value }}</p>\n </div>\n </clx-list-item>\n }\n </clx-list>\n </div>\n </ng-template>\n\n <!-- Tab: Especificaciones (st_spec_types \u2014 specs de la variaci\u00F3n activa) -->\n <ng-template [clxTabPanel]=\"'specs'\">\n <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-x-8\">\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (st of specsLeft(p.spec_types); track st.id) {\n <clx-list-item [label]=\"st.name + ':'\" [sublabel]=\"selectedOptionLabel(st)\" [icon]=\"st.icon ?? ''\" />\n }\n </clx-list>\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (st of specsRight(p.spec_types); track st.id) {\n <clx-list-item [label]=\"st.name + ':'\" [sublabel]=\"selectedOptionLabel(st)\" [icon]=\"st.icon ?? ''\" />\n }\n </clx-list>\n </div>\n </ng-template>\n\n <!-- Tab: Descripci\u00F3n (st_product_descriptions) -->\n <ng-template [clxTabPanel]=\"'description'\">\n <div class=\"space-y-5\">\n @for (block of p.descriptions; track block.id) {\n <div>\n @if (block.title) {\n <h3 class=\"text-base font-semibold text-clx-text-label mb-1\">{{ block.title }}</h3>\n }\n <p class=\"text-sm text-clx-text-muted leading-relaxed\">{{ block.body }}</p>\n </div>\n }\n </div>\n </ng-template>\n\n </clx-tabs>\n\n <!-- \u2500\u2500 Reviews \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"grid grid-cols-1 xl:grid-cols-3 gap-8 border-t border-clx-border pt-8\">\n\n <div class=\"space-y-3\">\n <h2 class=\"text-base font-bold text-clx-text-label\">Opiniones de clientes</h2>\n <div class=\"flex items-end gap-3\">\n <span class=\"text-5xl font-black text-clx-text-label\">{{ p.rating | number:'1.1-1' }}</span>\n <div class=\"pb-1 space-y-1\">\n <clx-rating [value]=\"p.rating\" [color]=\"clxColor()\"></clx-rating>\n <p class=\"text-xs text-clx-text-muted\">{{ p.review_count }} valoraciones verificadas</p>\n </div>\n </div>\n <p class=\"text-sm font-semibold\" [class]=\"_ratingLabelClass()\">{{ _ratingLabel() }}</p>\n <div class=\"pt-2 space-y-2\">\n <button clx-button type=\"button\" variant=\"solid\" [color]=\"_leaveReviewColor()\" [textColor]=\"leaveReviewTextColor()\" size=\"md\" [block]=\"true\" icon=\"rate_review\"\n (click)=\"leaveReview.emit()\">\n Dejar una rese\u00F1a\n </button>\n </div>\n </div>\n\n <div class=\"lg:col-span-2 space-y-4\">\n @for (review of p.reviews ?? []; track review.id) {\n <div [class]=\"_reviewCardClass()\">\n <div class=\"flex items-center gap-3 px-4 py-3\">\n <div class=\"w-10 h-10 rounded-full bg-clx-surface-2 flex items-center justify-center shrink-0\">\n <span class=\"text-sm font-bold text-clx-text-muted\">\n {{ review.author_name.substring(0,1) }}{{ review.author_name.split(' ')[1]?.substring(0,1) ?? '' }}\n </span>\n </div>\n <div class=\"flex-1 min-w-0\">\n <p class=\"font-semibold text-sm text-clx-text-label truncate\">{{ review.author_name }}</p>\n <p class=\"text-xs text-clx-text-subtle\">{{ review.created_at }}</p>\n </div>\n @if (review.is_verified) {\n <span clx-badge variant=\"light\" color=\"emerald\" size=\"sm\" shape=\"pill\">Verified</span>\n }\n </div>\n <div class=\"px-4 pb-4 space-y-2\">\n <div class=\"flex items-center gap-2\">\n <clx-rating [value]=\"review.rating\" [color]=\"clxColor()\"></clx-rating>\n @if (review.title) {\n <span class=\"text-sm font-semibold text-clx-text-label\">{{ review.title }}</span>\n }\n </div>\n @if (review.body) {\n <p class=\"text-sm text-clx-text-muted\">{{ review.body }}</p>\n }\n <div class=\"flex items-center gap-3 pt-1\">\n <span class=\"text-xs text-clx-text-subtle\">\u00BFTe result\u00F3 \u00FAtil?</span>\n <button clx-button type=\"button\" variant=\"ghost\" color=\"slate\" size=\"xs\" icon=\"thumb_up\">{{ review.helpful }}</button>\n <button clx-button type=\"button\" variant=\"ghost\" color=\"slate\" size=\"xs\" icon=\"thumb_down\">{{ review.not_helpful }}</button>\n </div>\n </div>\n </div>\n }\n </div>\n\n </div>\n }\n\n </div>\n}\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ClxButtonComponent, selector: "button[clx-button], a[clx-button]", inputs: ["variant", "color", "emphasis", "textColor", "size", "shape", "loading", "disabled", "block", "icon", "iconPosition", "iconOnly", "badge", "badgeColor"] }, { kind: "component", type: ClxButtonGroupComponent, selector: "clx-button-group", inputs: ["label", "shape", "attached", "orientation"] }, { kind: "component", type: ClxBadgeComponent, selector: "span[clx-badge]", inputs: ["variant", "color", "size", "shape", "positioned"] }, { kind: "component", type: ClxIconComponent, selector: "span[clx-icon]", inputs: ["name", "size", "color", "fill"] }, { kind: "component", type: ClxSkeletonComponent, selector: "clx-skeleton", inputs: ["variant", "size", "width", "height"] }, { kind: "component", type: ClxRatingComponent, selector: "clx-rating", inputs: ["value", "max", "color"] }, { kind: "component", type: ClxCarouselComponent, selector: "clx-carousel", inputs: ["clxColor", "autoPlay", "interval", "loop", "aspectRatio", "transparent"] }, { kind: "directive", type: ClxCarouselDirective, selector: "[clxSlide]" }, { kind: "component", type: ClxTabsComponent, selector: "clx-tabs", inputs: ["tabs", "clxColor", "activeTextColor", "underlineColor", "activeTab"], outputs: ["activeTabChange"] }, { kind: "directive", type: ClxTabDirective, selector: "[clxTabPanel]", inputs: ["clxTabPanel"] }, { kind: "component", type: ClxNumberComponent, selector: "clx-number", inputs: ["variant", "size", "color", "label", "placeholder", "hint", "prefixIcon", "min", "max", "step", "value", "disabled"] }, { kind: "directive", type: ClxTooltipDirective, selector: "[clxTooltip]", inputs: ["clxTooltip", "clxTooltipPosition", "clxTooltipColor", "clxTooltipSize", "clxTooltipDelay"] }, { kind: "component", type: ClxListComponent, selector: "clx-list", inputs: ["color", "variant", "size", "numbered"] }, { kind: "component", type: ClxListItemComponent, selector: "clx-list-item", inputs: ["label", "sublabel", "wrapSublabel", "icon", "meta", "metaColor", "disabled"] }, { kind: "pipe", type: CurrencyPipe, name: "currency" }, { kind: "pipe", type: DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
15981
15990
  }
15982
15991
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxProductDetailComponent, decorators: [{
15983
15992
  type: Component,
@@ -16000,7 +16009,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
16000
16009
  ClxListComponent,
16001
16010
  ClxListItemComponent,
16002
16011
  ], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- \u2550\u2550 SKELETON \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (loading()) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-8 p-6\">\n <div class=\"space-y-3\">\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"420px\" class=\"rounded-2xl\"></clx-skeleton>\n <div class=\"flex gap-2\">\n @for (t of [1,2,3,4]; track t) {\n <clx-skeleton variant=\"rectangular\" width=\"80px\" height=\"80px\" class=\"rounded-xl\"></clx-skeleton>\n }\n </div>\n </div>\n <div class=\"space-y-4 pt-2\">\n <clx-skeleton variant=\"text\" size=\"lg\" width=\"70%\"></clx-skeleton>\n <clx-skeleton variant=\"text\" size=\"sm\" width=\"40%\"></clx-skeleton>\n <clx-skeleton variant=\"text\" size=\"xl\" width=\"50%\"></clx-skeleton>\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"100px\" class=\"rounded-xl\"></clx-skeleton>\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"52px\" class=\"rounded-xl\"></clx-skeleton>\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"52px\" class=\"rounded-xl\"></clx-skeleton>\n </div>\n </div>\n}\n\n<!-- \u2550\u2550 CONTENT \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (!loading() && product(); as p) {\n @let v = activeVariation();\n <div class=\"space-y-8\">\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-8 md:items-start\">\n\n <!-- \u2500\u2500 Column 1: Image Gallery \u2014 each column sizes to its own content (no forced equal\n height, no position:sticky). A `sticky` gallery reads great in isolation, but a host app\n that already has its own `position: sticky` header (a full-width top bar, as opposed to\n this library's own `clx-app-layout` sidebar, which is `fixed` and never competes) forces\n the browser to keep recomputing both stickies' viewport-relative offsets together on every\n resize \u2014 confirmed as the source of a persistent resize-lag bug in codexly-store's product\n page. Not worth reintroducing until sticky positioning is made robust to an arbitrary host\n header. \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div [class]=\"_panelClass() + ' flex flex-col gap-3'\">\n <div class=\"relative aspect-4/3\">\n <div [class]=\"v && !v.in_stock ? 'opacity-40 grayscale' : ''\">\n <clx-carousel [clxColor]=\"clxColor()\" aspectRatio=\"4/3\" [loop]=\"true\">\n @for (img of v?.images ?? []; track i; let i = $index) {\n <ng-template clxSlide>\n <div class=\"w-full h-full p-5 sm:p-12.5 md:p-5 lg:p-12.5\">\n <img [src]=\"img\" [alt]=\"p.product_name\" class=\"w-full h-full object-contain\"\n [loading]=\"i === 0 ? 'eager' : 'lazy'\"\n [attr.fetchpriority]=\"i === 0 ? 'high' : null\" />\n </div>\n </ng-template>\n }\n </clx-carousel>\n </div>\n @if (v && !v.in_stock) {\n <div class=\"absolute inset-0 flex items-center justify-center pointer-events-none z-10\">\n <span clx-badge variant=\"solid\" color=\"slate\" size=\"md\" shape=\"pill\">Agotado</span>\n </div>\n }\n <div class=\"absolute top-3 right-3 z-10\">\n <button clx-button type=\"button\" [variant]=\"wishlistVariant()\" [color]=\"_wishlistColor()\" [textColor]=\"wishlistTextColor()\" shape=\"circle\" size=\"md\"\n [iconOnly]=\"true\" clxTooltip=\"Agregar a favoritos\" [clxTooltipColor]=\"_wishlistColor()\" clxTooltipPosition=\"left\"\n (click)=\"wishlist.emit()\">\n <span clx-icon name=\"favorite_border\" size=\"sm\"></span>\n </button>\n </div>\n </div>\n <div class=\"flex flex-wrap gap-2\">\n @for (img of v?.images ?? []; track i; let i = $index) {\n <img [src]=\"img\" [alt]=\"p.product_name\"\n class=\"w-20 h-20 rounded-xl object-cover border-[3px] cursor-pointer shrink-0 transition-[border-color,opacity]\"\n [class]=\"_carousel()?.currentPageIndex() === i\n ? _thumbBorderClass() + ' opacity-100'\n : 'border-transparent opacity-60 hover:opacity-90'\"\n loading=\"lazy\"\n (click)=\"_carousel()?.goTo(i)\" />\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Column 2: Purchase Info \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div [class]=\"_panelClass() + ' space-y-2.5'\">\n\n <!-- Stock status -->\n @if (v) {\n <span clx-badge variant=\"solid\" [color]=\"v.in_stock ? _stockBadgeColor() : 'red'\" size=\"sm\" shape=\"pill\">\n @if (v.in_stock) {\n @if (v.stock_quantity != null) {\n {{ v.stock_quantity }} en stock\n } @else {\n En stock\n }\n } @else {\n Sin stock\n }\n </span>\n }\n\n <!-- Tags (st_products.tags) -->\n <div class=\"flex flex-wrap gap-1\">\n @for (tag of p.tags; track tag) {\n <span clx-badge [variant]=\"tagVariant(tag)\" [color]=\"tagColor(tag)\" size=\"sm\" shape=\"pill\">\n {{ tagLabel(tag) }}\n </span>\n }\n </div>\n\n <!-- Title -->\n <div>\n <p class=\"text-sm text-clx-text-subtle uppercase tracking-wide font-medium\">{{ p.trademark }}</p>\n <h1 class=\"text-2xl font-bold text-clx-text-label leading-tight mt-0.5\">{{ v?.name || p.product_name }}</h1>\n @if (p.product_detail) {\n <p class=\"text-clx-text-muted text-sm mt-1\">{{ p.product_detail }}</p>\n }\n </div>\n\n <!-- Rating -->\n <div class=\"flex items-center gap-2\">\n <clx-rating [value]=\"p.rating\" [color]=\"clxColor()\"></clx-rating>\n <span class=\"text-sm font-semibold text-clx-text-label\">{{ p.rating | number:'1.1-1' }}</span>\n <span class=\"text-sm text-clx-text-subtle\">({{ p.review_count }} reviews)</span>\n </div>\n\n <!-- Price block -->\n <div>\n <div class=\"flex items-baseline gap-3\">\n <span class=\"text-2xl font-black text-clx-text-label\">\n {{ finalPrice() | currency:currency():'$':'1.0-0' }}\n </span>\n @if (p.has_discount && p.discount_price) {\n <span class=\"text-base text-clx-text-subtle line-through\">\n {{ p.discount_price | currency:currency():'$':'1.0-0' }}\n </span>\n <span clx-badge variant=\"solid\" [color]=\"_discountBadgeColor()\" size=\"sm\" shape=\"pill\">\n -{{ discount() }}%\n </span>\n }\n </div>\n </div>\n\n <!-- Selectores de variaci\u00F3n (st_spec_types + st_spec_options) \u2014 opciones no disponibles\n para la combinaci\u00F3n actual (ej. Talla S con Color Rojo) se ocultan por completo en vez\n de mostrarse deshabilitadas; reaparecen si el usuario cambia a una combinaci\u00F3n donde s\u00ED\n aplican, ya que isOptionAvailable() se reeval\u00FAa contra selectedSpecs() en cada cambio. -->\n @for (specType of p.spec_types; track specType.id) {\n @if (specType.input_type === 'color') {\n <clx-button-group [label]=\"specType.name\" shape=\"circular\" [attached]=\"false\">\n @for (opt of specType.options; track opt.id) {\n @if (isOptionAvailable(specType.id, opt.id)) {\n <button\n type=\"button\"\n class=\"w-7 h-7 rounded-full border-2 transition-[transform,box-shadow] shrink-0 cursor-pointer\"\n [class]=\"isSpecSelected(specType.id, opt.id) ? 'scale-110 shadow-md' : 'opacity-90 hover:opacity-100'\"\n [style.background-color]=\"opt.value\"\n [style.border-color]=\"isSpecSelected(specType.id, opt.id) ? 'var(--clx-text-label, #111)' : 'transparent'\"\n [clxTooltip]=\"opt.label ?? opt.value\"\n clxTooltipPosition=\"top\"\n (click)=\"selectSpec(specType.id, opt.id)\">\n </button>\n }\n }\n </clx-button-group>\n } @else {\n <clx-button-group [label]=\"specType.name\" shape=\"flat\" [attached]=\"false\">\n @for (opt of specType.options; track opt.id) {\n @if (isOptionAvailable(specType.id, opt.id)) {\n <button clx-button\n type=\"button\"\n [variant]=\"isSpecSelected(specType.id, opt.id) ? 'solid' : 'outline'\"\n [color]=\"_specButtonColor()\"\n [textColor]=\"isSpecSelected(specType.id, opt.id) ? specButtonTextColor() : undefined\"\n size=\"sm\"\n (click)=\"selectSpec(specType.id, opt.id)\">\n {{ opt.label ?? opt.value }}\n </button>\n }\n }\n </clx-button-group>\n }\n }\n\n <!-- Quantity -->\n <clx-number label=\"Cantidad:\" variant=\"stepper\" [color]=\"_quantityColor()\"\n [min]=\"1\" [max]=\"99\" [ngModel]=\"quantity()\" (ngModelChange)=\"quantity.set($event)\">\n </clx-number>\n\n <!-- CTA -->\n @if (v?.in_stock && !isSelectionComplete()) {\n <p class=\"text-sm text-red-500\">Selecciona {{ missingSpecNames().join(' y ') }} para continuar.</p>\n }\n <div class=\"flex gap-3\">\n <button clx-button type=\"button\" variant=\"solid\" [color]=\"_buyNowColor()\" [textColor]=\"buyNowTextColor()\" size=\"md\" [block]=\"true\" icon=\"bolt\"\n [disabled]=\"!v?.in_stock || !isSelectionComplete()\" (click)=\"_emitBuyNow()\">Comprar ahora</button>\n <button clx-button type=\"button\" variant=\"light\" emphasis=\"secondary\" [color]=\"addToCartColor()\" [textColor]=\"addToCartTextColor()\" size=\"md\" [block]=\"true\" icon=\"shopping_cart\"\n [disabled]=\"!v?.in_stock || !isSelectionComplete()\" (click)=\"_emitAddToCart()\">Agrega al carrito</button>\n </div>\n\n </div>\n </div>\n\n @if (!compact()) {\n <!-- \u2500\u2500 Tabs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <clx-tabs [tabs]=\"tabItems\" [clxColor]=\"clxColor()\" [(activeTab)]=\"activeTab\">\n\n <!-- Tab: Caracter\u00EDsticas generales (st_product_specs \u2014 ficha t\u00E9cnica global) -->\n <ng-template [clxTabPanel]=\"'general'\">\n <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-x-8\">\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (spec of specsLeft(p.product_specs); track spec.id) {\n <clx-list-item [icon]=\"spec.icon ?? ''\">\n <div clxContent>\n <p class=\"text-sm font-medium text-clx-text-label\">{{ spec.name }}:</p>\n <p class=\"text-sm text-clx-text-muted mt-0.5\">{{ spec.value }}</p>\n </div>\n </clx-list-item>\n }\n </clx-list>\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (spec of specsRight(p.product_specs); track spec.id) {\n <clx-list-item [icon]=\"spec.icon ?? ''\">\n <div clxContent>\n <p class=\"text-sm font-medium text-clx-text-label\">{{ spec.name }}:</p>\n <p class=\"text-sm text-clx-text-muted mt-0.5\">{{ spec.value }}</p>\n </div>\n </clx-list-item>\n }\n </clx-list>\n </div>\n </ng-template>\n\n <!-- Tab: Especificaciones (st_spec_types \u2014 specs de la variaci\u00F3n activa) -->\n <ng-template [clxTabPanel]=\"'specs'\">\n <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-x-8\">\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (st of specsLeft(p.spec_types); track st.id) {\n <clx-list-item [label]=\"st.name + ':'\" [sublabel]=\"selectedOptionLabel(st)\" [icon]=\"st.icon ?? ''\" />\n }\n </clx-list>\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (st of specsRight(p.spec_types); track st.id) {\n <clx-list-item [label]=\"st.name + ':'\" [sublabel]=\"selectedOptionLabel(st)\" [icon]=\"st.icon ?? ''\" />\n }\n </clx-list>\n </div>\n </ng-template>\n\n <!-- Tab: Descripci\u00F3n (st_product_descriptions) -->\n <ng-template [clxTabPanel]=\"'description'\">\n <div class=\"space-y-5\">\n @for (block of p.descriptions; track block.id) {\n <div>\n @if (block.title) {\n <h3 class=\"text-base font-semibold text-clx-text-label mb-1\">{{ block.title }}</h3>\n }\n <p class=\"text-sm text-clx-text-muted leading-relaxed\">{{ block.body }}</p>\n </div>\n }\n </div>\n </ng-template>\n\n </clx-tabs>\n\n <!-- \u2500\u2500 Reviews \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"grid grid-cols-1 xl:grid-cols-3 gap-8 border-t border-clx-border pt-8\">\n\n <div class=\"space-y-3\">\n <h2 class=\"text-base font-bold text-clx-text-label\">Opiniones de clientes</h2>\n <div class=\"flex items-end gap-3\">\n <span class=\"text-5xl font-black text-clx-text-label\">{{ p.rating | number:'1.1-1' }}</span>\n <div class=\"pb-1 space-y-1\">\n <clx-rating [value]=\"p.rating\" [color]=\"clxColor()\"></clx-rating>\n <p class=\"text-xs text-clx-text-muted\">{{ p.review_count }} valoraciones verificadas</p>\n </div>\n </div>\n <p class=\"text-sm font-semibold\" [class]=\"_ratingLabelClass()\">{{ _ratingLabel() }}</p>\n <div class=\"pt-2 space-y-2\">\n <button clx-button type=\"button\" variant=\"solid\" [color]=\"_leaveReviewColor()\" [textColor]=\"leaveReviewTextColor()\" size=\"md\" [block]=\"true\" icon=\"rate_review\"\n (click)=\"leaveReview.emit()\">\n Dejar una rese\u00F1a\n </button>\n </div>\n </div>\n\n <div class=\"lg:col-span-2 space-y-4\">\n @for (review of p.reviews ?? []; track review.id) {\n <div [class]=\"_reviewCardClass()\">\n <div class=\"flex items-center gap-3 px-4 py-3\">\n <div class=\"w-10 h-10 rounded-full bg-clx-surface-2 flex items-center justify-center shrink-0\">\n <span class=\"text-sm font-bold text-clx-text-muted\">\n {{ review.author_name.substring(0,1) }}{{ review.author_name.split(' ')[1]?.substring(0,1) ?? '' }}\n </span>\n </div>\n <div class=\"flex-1 min-w-0\">\n <p class=\"font-semibold text-sm text-clx-text-label truncate\">{{ review.author_name }}</p>\n <p class=\"text-xs text-clx-text-subtle\">{{ review.created_at }}</p>\n </div>\n @if (review.is_verified) {\n <span clx-badge variant=\"light\" color=\"emerald\" size=\"sm\" shape=\"pill\">Verified</span>\n }\n </div>\n <div class=\"px-4 pb-4 space-y-2\">\n <div class=\"flex items-center gap-2\">\n <clx-rating [value]=\"review.rating\" [color]=\"clxColor()\"></clx-rating>\n @if (review.title) {\n <span class=\"text-sm font-semibold text-clx-text-label\">{{ review.title }}</span>\n }\n </div>\n @if (review.body) {\n <p class=\"text-sm text-clx-text-muted\">{{ review.body }}</p>\n }\n <div class=\"flex items-center gap-3 pt-1\">\n <span class=\"text-xs text-clx-text-subtle\">\u00BFTe result\u00F3 \u00FAtil?</span>\n <button clx-button type=\"button\" variant=\"ghost\" color=\"slate\" size=\"xs\" icon=\"thumb_up\">{{ review.helpful }}</button>\n <button clx-button type=\"button\" variant=\"ghost\" color=\"slate\" size=\"xs\" icon=\"thumb_down\">{{ review.not_helpful }}</button>\n </div>\n </div>\n </div>\n }\n </div>\n\n </div>\n }\n\n </div>\n}\n" }]
16003
- }], ctorParameters: () => [], propDecorators: { product: [{ type: i0.Input, args: [{ isSignal: true, alias: "product", required: false }] }], initialVariationId: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialVariationId", required: false }] }], clxColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "clxColor", required: false }] }], buyNowColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "buyNowColor", required: false }] }], buyNowTextColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "buyNowTextColor", required: false }] }], addToCartColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "addToCartColor", required: false }] }], addToCartTextColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "addToCartTextColor", required: false }] }], leaveReviewColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "leaveReviewColor", required: false }] }], leaveReviewTextColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "leaveReviewTextColor", required: false }] }], wishlistColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "wishlistColor", required: false }] }], wishlistTextColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "wishlistTextColor", required: false }] }], wishlistVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "wishlistVariant", required: false }] }], stockBadgeColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "stockBadgeColor", required: false }] }], discountBadgeColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "discountBadgeColor", required: false }] }], quantityColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "quantityColor", required: false }] }], specButtonColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "specButtonColor", required: false }] }], specButtonTextColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "specButtonTextColor", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], currency: [{ type: i0.Input, args: [{ isSignal: true, alias: "currency", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], shadow: [{ type: i0.Input, args: [{ isSignal: true, alias: "shadow", required: false }] }], addToCart: [{ type: i0.Output, args: ["addToCart"] }], buyNow: [{ type: i0.Output, args: ["buyNow"] }], wishlist: [{ type: i0.Output, args: ["wishlist"] }], variationChange: [{ type: i0.Output, args: ["variationChange"] }], leaveReview: [{ type: i0.Output, args: ["leaveReview"] }], _carousel: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ClxCarouselComponent), { isSignal: true }] }] } });
16012
+ }], ctorParameters: () => [], propDecorators: { product: [{ type: i0.Input, args: [{ isSignal: true, alias: "product", required: false }] }], initialVariationId: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialVariationId", required: false }] }], clxColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "clxColor", required: false }] }], buyNowColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "buyNowColor", required: false }] }], buyNowTextColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "buyNowTextColor", required: false }] }], addToCartColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "addToCartColor", required: false }] }], addToCartTextColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "addToCartTextColor", required: false }] }], leaveReviewColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "leaveReviewColor", required: false }] }], leaveReviewTextColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "leaveReviewTextColor", required: false }] }], wishlistColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "wishlistColor", required: false }] }], wishlistTextColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "wishlistTextColor", required: false }] }], wishlistVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "wishlistVariant", required: false }] }], stockBadgeColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "stockBadgeColor", required: false }] }], discountBadgeColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "discountBadgeColor", required: false }] }], quantityColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "quantityColor", required: false }] }], specButtonColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "specButtonColor", required: false }] }], specButtonTextColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "specButtonTextColor", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], currency: [{ type: i0.Input, args: [{ isSignal: true, alias: "currency", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], addToCart: [{ type: i0.Output, args: ["addToCart"] }], buyNow: [{ type: i0.Output, args: ["buyNow"] }], wishlist: [{ type: i0.Output, args: ["wishlist"] }], variationChange: [{ type: i0.Output, args: ["variationChange"] }], leaveReview: [{ type: i0.Output, args: ["leaveReview"] }], _carousel: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ClxCarouselComponent), { isSignal: true }] }] } });
16004
16013
 
16005
16014
  class ClxProductQuickViewComponent {
16006
16015
  ref = inject(CLX_MODAL_REF, { optional: true });
@@ -16022,7 +16031,7 @@ class ClxProductQuickViewComponent {
16022
16031
  (addToCart)="_onAddToCart($event)">
16023
16032
  </clx-product-detail>
16024
16033
  </clx-modal>
16025
- `, isInline: true, dependencies: [{ kind: "component", type: ClxModalComponent, selector: "clx-modal", inputs: ["size", "showClose", "confirmButton", "cancelButton", "showCancelButton"], outputs: ["confirmClick", "cancelClick"] }, { kind: "component", type: ClxProductDetailComponent, selector: "clx-product-detail", inputs: ["product", "initialVariationId", "clxColor", "buyNowColor", "buyNowTextColor", "addToCartColor", "addToCartTextColor", "leaveReviewColor", "leaveReviewTextColor", "wishlistColor", "wishlistTextColor", "wishlistVariant", "stockBadgeColor", "discountBadgeColor", "quantityColor", "specButtonColor", "specButtonTextColor", "loading", "currency", "compact", "shadow"], outputs: ["addToCart", "buyNow", "wishlist", "variationChange", "leaveReview"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
16034
+ `, isInline: true, dependencies: [{ kind: "component", type: ClxModalComponent, selector: "clx-modal", inputs: ["size", "showClose", "confirmButton", "cancelButton", "showCancelButton"], outputs: ["confirmClick", "cancelClick"] }, { kind: "component", type: ClxProductDetailComponent, selector: "clx-product-detail", inputs: ["product", "initialVariationId", "clxColor", "buyNowColor", "buyNowTextColor", "addToCartColor", "addToCartTextColor", "leaveReviewColor", "leaveReviewTextColor", "wishlistColor", "wishlistTextColor", "wishlistVariant", "stockBadgeColor", "discountBadgeColor", "quantityColor", "specButtonColor", "specButtonTextColor", "loading", "currency", "compact", "variant"], outputs: ["addToCart", "buyNow", "wishlist", "variationChange", "leaveReview"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
16026
16035
  }
16027
16036
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxProductQuickViewComponent, decorators: [{
16028
16037
  type: Component,
@@ -16084,9 +16093,10 @@ class ClxWishlistComponent {
16084
16093
  actionColor = input(undefined, ...(ngDevMode ? [{ debugName: "actionColor" }] : /* istanbul ignore next */ []));
16085
16094
  actionTextColor = input(undefined, ...(ngDevMode ? [{ debugName: "actionTextColor" }] : /* istanbul ignore next */ []));
16086
16095
  _actionColor = computed(() => this.actionColor() ?? this.color() ?? this._themeSvc.config().primaryColor, ...(ngDevMode ? [{ debugName: "_actionColor" }] : /* istanbul ignore next */ []));
16087
- /** Product card drop shadow — same ClxCardShadow scale as clx-card ('none' turns it off
16088
- * entirely). Falls back to 'sm', the shadow these cards have always rendered with. */
16089
- shadow = input(undefined, ...(ngDevMode ? [{ debugName: "shadow" }] : /* istanbul ignore next */ []));
16096
+ /** Same ClxCardVariant concept as clx-card: 'elevated' (border + shadow, the default these
16097
+ * cards have always rendered with), 'outlined' (border, no shadow), 'flat' (neither). The
16098
+ * border itself always reads cardBorderColor() regardless of variant. */
16099
+ variant = input('elevated', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
16090
16100
  sortOptions = input(WISHLIST_SORT_OPTIONS, ...(ngDevMode ? [{ debugName: "sortOptions" }] : /* istanbul ignore next */ []));
16091
16101
  currentPage = model(1, ...(ngDevMode ? [{ debugName: "currentPage" }] : /* istanbul ignore next */ []));
16092
16102
  pageSize = model(WISHLIST_PAGE_SIZE_DEFAULT, ...(ngDevMode ? [{ debugName: "pageSize" }] : /* istanbul ignore next */ []));
@@ -16112,9 +16122,11 @@ class ClxWishlistComponent {
16112
16122
  * elevated variant — so a host passing an explicit `color` doesn't also drag the card border
16113
16123
  * along with it. */
16114
16124
  _cardClass = computed(() => {
16125
+ const v = this.variant();
16115
16126
  const t = resolveColor(this._themeSvc.cardBorderColor());
16116
- const shadowCls = CARD_SHADOW_MAP[this.shadow() ?? 'sm'];
16117
- return `bg-clx-surface rounded-2xl ${shadowCls} border ${t.borderLight} transition-[box-shadow,transform] duration-200 overflow-hidden group hover:shadow-md hover:-translate-y-0.5 flex flex-col sm:flex-row`;
16127
+ const borderCls = v === 'flat' ? 'border border-transparent' : `border ${t.borderLight}`;
16128
+ const shadowCls = v === 'elevated' ? 'shadow-sm' : '';
16129
+ return `bg-clx-surface rounded-2xl ${shadowCls} ${borderCls} transition-[box-shadow,transform] duration-200 overflow-hidden group hover:shadow-md hover:-translate-y-0.5 flex flex-col sm:flex-row`;
16118
16130
  }, ...(ngDevMode ? [{ debugName: "_cardClass" }] : /* istanbul ignore next */ []));
16119
16131
  _imageContainerClass = 'relative shrink-0 w-full p-2.5 aspect-square sm:aspect-auto sm:w-40 overflow-hidden rounded-t-2xl sm:rounded-t-none sm:rounded-l-2xl';
16120
16132
  _imageClass = 'w-full h-full object-cover transition-transform duration-500 group-hover:scale-105';
@@ -16185,7 +16197,7 @@ class ClxWishlistComponent {
16185
16197
  this.onCheckoutAll.emit(this._filtered());
16186
16198
  }
16187
16199
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxWishlistComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
16188
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxWishlistComponent, isStandalone: true, selector: "clx-wishlist", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, checkoutAllColor: { classPropertyName: "checkoutAllColor", publicName: "checkoutAllColor", isSignal: true, isRequired: false, transformFunction: null }, checkoutAllTextColor: { classPropertyName: "checkoutAllTextColor", publicName: "checkoutAllTextColor", isSignal: true, isRequired: false, transformFunction: null }, checkoutAllEmphasis: { classPropertyName: "checkoutAllEmphasis", publicName: "checkoutAllEmphasis", isSignal: true, isRequired: false, transformFunction: null }, actionColor: { classPropertyName: "actionColor", publicName: "actionColor", isSignal: true, isRequired: false, transformFunction: null }, actionTextColor: { classPropertyName: "actionTextColor", publicName: "actionTextColor", isSignal: true, isRequired: false, transformFunction: null }, shadow: { classPropertyName: "shadow", publicName: "shadow", isSignal: true, isRequired: false, transformFunction: null }, sortOptions: { classPropertyName: "sortOptions", publicName: "sortOptions", isSignal: true, isRequired: false, transformFunction: null }, currentPage: { classPropertyName: "currentPage", publicName: "currentPage", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, sortValue: { classPropertyName: "sortValue", publicName: "sortValue", isSignal: true, isRequired: false, transformFunction: null }, searchValue: { classPropertyName: "searchValue", publicName: "searchValue", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { currentPage: "currentPageChange", pageSize: "pageSizeChange", sortValue: "sortValueChange", searchValue: "searchValueChange", onRemove: "onRemove", onAddToCart: "onAddToCart", onSearchChange: "onSearchChange", onSortChange: "onSortChange", onPageChange: "onPageChange", onCheckoutAll: "onCheckoutAll", onBrowseCatalog: "onBrowseCatalog" }, ngImport: i0, template: `
16200
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxWishlistComponent, isStandalone: true, selector: "clx-wishlist", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, checkoutAllColor: { classPropertyName: "checkoutAllColor", publicName: "checkoutAllColor", isSignal: true, isRequired: false, transformFunction: null }, checkoutAllTextColor: { classPropertyName: "checkoutAllTextColor", publicName: "checkoutAllTextColor", isSignal: true, isRequired: false, transformFunction: null }, checkoutAllEmphasis: { classPropertyName: "checkoutAllEmphasis", publicName: "checkoutAllEmphasis", isSignal: true, isRequired: false, transformFunction: null }, actionColor: { classPropertyName: "actionColor", publicName: "actionColor", isSignal: true, isRequired: false, transformFunction: null }, actionTextColor: { classPropertyName: "actionTextColor", publicName: "actionTextColor", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, sortOptions: { classPropertyName: "sortOptions", publicName: "sortOptions", isSignal: true, isRequired: false, transformFunction: null }, currentPage: { classPropertyName: "currentPage", publicName: "currentPage", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, sortValue: { classPropertyName: "sortValue", publicName: "sortValue", isSignal: true, isRequired: false, transformFunction: null }, searchValue: { classPropertyName: "searchValue", publicName: "searchValue", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { currentPage: "currentPageChange", pageSize: "pageSizeChange", sortValue: "sortValueChange", searchValue: "searchValueChange", onRemove: "onRemove", onAddToCart: "onAddToCart", onSearchChange: "onSearchChange", onSortChange: "onSortChange", onPageChange: "onPageChange", onCheckoutAll: "onCheckoutAll", onBrowseCatalog: "onBrowseCatalog" }, ngImport: i0, template: `
16189
16201
  <div class="flex flex-col gap-6">
16190
16202
 
16191
16203
  @if (loading() || items().length > 0) {
@@ -16590,7 +16602,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
16590
16602
  encapsulation: ViewEncapsulation.None,
16591
16603
  changeDetection: ChangeDetectionStrategy.OnPush,
16592
16604
  }]
16593
- }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], checkoutAllColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "checkoutAllColor", required: false }] }], checkoutAllTextColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "checkoutAllTextColor", required: false }] }], checkoutAllEmphasis: [{ type: i0.Input, args: [{ isSignal: true, alias: "checkoutAllEmphasis", required: false }] }], actionColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionColor", required: false }] }], actionTextColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionTextColor", required: false }] }], shadow: [{ type: i0.Input, args: [{ isSignal: true, alias: "shadow", required: false }] }], sortOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortOptions", required: false }] }], currentPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentPage", required: false }] }, { type: i0.Output, args: ["currentPageChange"] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }, { type: i0.Output, args: ["pageSizeChange"] }], sortValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortValue", required: false }] }, { type: i0.Output, args: ["sortValueChange"] }], searchValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchValue", required: false }] }, { type: i0.Output, args: ["searchValueChange"] }], onRemove: [{ type: i0.Output, args: ["onRemove"] }], onAddToCart: [{ type: i0.Output, args: ["onAddToCart"] }], onSearchChange: [{ type: i0.Output, args: ["onSearchChange"] }], onSortChange: [{ type: i0.Output, args: ["onSortChange"] }], onPageChange: [{ type: i0.Output, args: ["onPageChange"] }], onCheckoutAll: [{ type: i0.Output, args: ["onCheckoutAll"] }], onBrowseCatalog: [{ type: i0.Output, args: ["onBrowseCatalog"] }] } });
16605
+ }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], checkoutAllColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "checkoutAllColor", required: false }] }], checkoutAllTextColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "checkoutAllTextColor", required: false }] }], checkoutAllEmphasis: [{ type: i0.Input, args: [{ isSignal: true, alias: "checkoutAllEmphasis", required: false }] }], actionColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionColor", required: false }] }], actionTextColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionTextColor", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], sortOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortOptions", required: false }] }], currentPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentPage", required: false }] }, { type: i0.Output, args: ["currentPageChange"] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }, { type: i0.Output, args: ["pageSizeChange"] }], sortValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortValue", required: false }] }, { type: i0.Output, args: ["sortValueChange"] }], searchValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchValue", required: false }] }, { type: i0.Output, args: ["searchValueChange"] }], onRemove: [{ type: i0.Output, args: ["onRemove"] }], onAddToCart: [{ type: i0.Output, args: ["onAddToCart"] }], onSearchChange: [{ type: i0.Output, args: ["onSearchChange"] }], onSortChange: [{ type: i0.Output, args: ["onSortChange"] }], onPageChange: [{ type: i0.Output, args: ["onPageChange"] }], onCheckoutAll: [{ type: i0.Output, args: ["onCheckoutAll"] }], onBrowseCatalog: [{ type: i0.Output, args: ["onBrowseCatalog"] }] } });
16594
16606
 
16595
16607
  class ClxCartComponent {
16596
16608
  items = input([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
@@ -16603,26 +16615,30 @@ class ClxCartComponent {
16603
16615
  * showing a code input that always fails would read as broken, not "not built yet". */
16604
16616
  showCoupon = input(true, ...(ngDevMode ? [{ debugName: "showCoupon" }] : /* istanbul ignore next */ []));
16605
16617
  coupon = model(null, ...(ngDevMode ? [{ debugName: "coupon" }] : /* istanbul ignore next */ []));
16606
- /** Drop shadow for the item cards and Order Summary panel same ClxCardShadow scale as
16607
- * clx-card ('none' turns it off entirely). Falls back to 'sm', the shadow these have always
16608
- * rendered with. */
16609
- shadow = input(undefined, ...(ngDevMode ? [{ debugName: "shadow" }] : /* istanbul ignore next */ []));
16618
+ /** Same ClxCardVariant concept as clx-card: 'elevated' (border + shadow, the default the item
16619
+ * cards and Order Summary panel have always rendered with), 'outlined' (border, no shadow),
16620
+ * 'flat' (neither). The border itself always reads cardBorderColor() regardless of variant. */
16621
+ variant = input('elevated', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
16610
16622
  _themeSvc = inject(ClxThemeService);
16611
16623
  _quantityColor = computed(() => this.quantityColor() ?? this.color(), ...(ngDevMode ? [{ debugName: "_quantityColor" }] : /* istanbul ignore next */ []));
16612
16624
  /** Item cards — was bg-clx-surface + shadow-sm with NO border at all (unlike clx-product/
16613
- * clx-wishlist, which already read cardBorderColor). Now consistent: themed border +
16614
- * configurable shadow, same borderLight treatment as clx-card's own elevated variant. */
16625
+ * clx-wishlist, which already read cardBorderColor). Now consistent: themed border, shadow
16626
+ * driven by variant, same borderLight treatment as clx-card's own elevated variant. */
16615
16627
  _cardClass = computed(() => {
16628
+ const v = this.variant();
16616
16629
  const t = resolveColor(this._themeSvc.cardBorderColor());
16617
- const shadowCls = CARD_SHADOW_MAP[this.shadow() ?? 'sm'];
16618
- return `bg-clx-surface rounded-2xl ${shadowCls} border ${t.borderLight} flex gap-0 overflow-hidden group transition-[box-shadow,transform] duration-200 hover:shadow-md hover:-translate-y-0.5`;
16630
+ const borderCls = v === 'flat' ? 'border border-transparent' : `border ${t.borderLight}`;
16631
+ const shadowCls = v === 'elevated' ? 'shadow-sm' : '';
16632
+ return `bg-clx-surface rounded-2xl ${shadowCls} ${borderCls} flex gap-0 overflow-hidden group transition-[box-shadow,transform] duration-200 hover:shadow-md hover:-translate-y-0.5`;
16619
16633
  }, ...(ngDevMode ? [{ debugName: "_cardClass" }] : /* istanbul ignore next */ []));
16620
16634
  /** Order Summary panel — same border/shadow treatment as _cardClass, but no hover lift (it's a
16621
16635
  * static panel, not a clickable card). */
16622
16636
  _summaryClass = computed(() => {
16637
+ const v = this.variant();
16623
16638
  const t = resolveColor(this._themeSvc.cardBorderColor());
16624
- const shadowCls = CARD_SHADOW_MAP[this.shadow() ?? 'sm'];
16625
- return `bg-clx-surface rounded-2xl ${shadowCls} border ${t.borderLight} p-5 flex flex-col gap-4`;
16639
+ const borderCls = v === 'flat' ? 'border border-transparent' : `border ${t.borderLight}`;
16640
+ const shadowCls = v === 'elevated' ? 'shadow-sm' : '';
16641
+ return `bg-clx-surface rounded-2xl ${shadowCls} ${borderCls} p-5 flex flex-col gap-4`;
16626
16642
  }, ...(ngDevMode ? [{ debugName: "_summaryClass" }] : /* istanbul ignore next */ []));
16627
16643
  onRemove = output();
16628
16644
  onQuantityChange = output();
@@ -16659,7 +16675,7 @@ class ClxCartComponent {
16659
16675
  this.coupon.set(null);
16660
16676
  }
16661
16677
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxCartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
16662
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxCartComponent, isStandalone: true, selector: "clx-cart", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, quantityColor: { classPropertyName: "quantityColor", publicName: "quantityColor", isSignal: true, isRequired: false, transformFunction: null }, showCoupon: { classPropertyName: "showCoupon", publicName: "showCoupon", isSignal: true, isRequired: false, transformFunction: null }, coupon: { classPropertyName: "coupon", publicName: "coupon", isSignal: true, isRequired: false, transformFunction: null }, shadow: { classPropertyName: "shadow", publicName: "shadow", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { coupon: "couponChange", onRemove: "onRemove", onQuantityChange: "onQuantityChange", onClearCart: "onClearCart", onApplyCoupon: "onApplyCoupon", onCheckout: "onCheckout", onBrowseCatalog: "onBrowseCatalog" }, ngImport: i0, template: `
16678
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxCartComponent, isStandalone: true, selector: "clx-cart", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, quantityColor: { classPropertyName: "quantityColor", publicName: "quantityColor", isSignal: true, isRequired: false, transformFunction: null }, showCoupon: { classPropertyName: "showCoupon", publicName: "showCoupon", isSignal: true, isRequired: false, transformFunction: null }, coupon: { classPropertyName: "coupon", publicName: "coupon", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { coupon: "couponChange", onRemove: "onRemove", onQuantityChange: "onQuantityChange", onClearCart: "onClearCart", onApplyCoupon: "onApplyCoupon", onCheckout: "onCheckout", onBrowseCatalog: "onBrowseCatalog" }, ngImport: i0, template: `
16663
16679
  <!-- ── Loading ─────────────────────────────────────────────────────────────── -->
16664
16680
  @if (loading()) {
16665
16681
 
@@ -17203,7 +17219,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
17203
17219
  }
17204
17220
  `,
17205
17221
  }]
17206
- }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], quantityColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "quantityColor", required: false }] }], showCoupon: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCoupon", required: false }] }], coupon: [{ type: i0.Input, args: [{ isSignal: true, alias: "coupon", required: false }] }, { type: i0.Output, args: ["couponChange"] }], shadow: [{ type: i0.Input, args: [{ isSignal: true, alias: "shadow", required: false }] }], onRemove: [{ type: i0.Output, args: ["onRemove"] }], onQuantityChange: [{ type: i0.Output, args: ["onQuantityChange"] }], onClearCart: [{ type: i0.Output, args: ["onClearCart"] }], onApplyCoupon: [{ type: i0.Output, args: ["onApplyCoupon"] }], onCheckout: [{ type: i0.Output, args: ["onCheckout"] }], onBrowseCatalog: [{ type: i0.Output, args: ["onBrowseCatalog"] }] } });
17222
+ }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], quantityColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "quantityColor", required: false }] }], showCoupon: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCoupon", required: false }] }], coupon: [{ type: i0.Input, args: [{ isSignal: true, alias: "coupon", required: false }] }, { type: i0.Output, args: ["couponChange"] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], onRemove: [{ type: i0.Output, args: ["onRemove"] }], onQuantityChange: [{ type: i0.Output, args: ["onQuantityChange"] }], onClearCart: [{ type: i0.Output, args: ["onClearCart"] }], onApplyCoupon: [{ type: i0.Output, args: ["onApplyCoupon"] }], onCheckout: [{ type: i0.Output, args: ["onCheckout"] }], onBrowseCatalog: [{ type: i0.Output, args: ["onBrowseCatalog"] }] } });
17207
17223
 
17208
17224
  class ClxCartSummaryDrawer {
17209
17225
  data = inject(CLX_MODAL_DATA);