{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "flash-sale-offer-bar",
  "title": "Flash Sale Offer Bar",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/flash-sale-offer-bar/flash-sale-offer-bar.component.ts",
      "content": "// Limited-time flash sale countdown deal bar with stock claim progress meter, tiered\n// multi-buy volume discounts, and 1-click cart activation. Port of the React block 1:1.\nimport {\n  ChangeDetectionStrategy,\n  Component,\n  DestroyRef,\n  EventEmitter,\n  Input,\n  Output,\n  afterNextRender,\n  inject,\n  signal,\n} from '@angular/core'\nimport { Flame, LucideAngularModule, ShieldCheck, Tag } from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\n\nexport interface TierOffer {\n  id: string\n  quantity: number\n  discountPct: number\n  label: string\n  badgeText?: string\n  popular?: boolean\n}\n\nexport interface TimeLeft {\n  hours: string\n  minutes: string\n  seconds: string\n}\n\nconst DEFAULT_TIERS: TierOffer[] = [\n  { id: 't1', quantity: 1, discountPct: 15, label: 'Standard Pack', badgeText: 'Save $45' },\n  {\n    id: 't2',\n    quantity: 2,\n    discountPct: 25,\n    label: 'Duo Studio Bundle',\n    badgeText: 'Save $150 · Best Value',\n    popular: true,\n  },\n  { id: 't3', quantity: 3, discountPct: 35, label: 'Team / Lab Triad', badgeText: 'Save $315' },\n]\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-flash-sale-offer-bar, [ui-flash-sale-offer-bar]',\n  standalone: true,\n  // React renders the root <div> itself: the host stays out of layout and `class` goes to the root.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [LucideAngularModule, UiBadgeComponent, UiButtonComponent],\n  template: `\n    <div data-slot=\"flash-sale-offer-bar\" [class]=\"rootClass\">\n      <!-- Deal Header Banner -->\n      <div\n        class=\"border-border bg-muted/40 flex flex-col gap-3 border-b p-4 sm:flex-row sm:items-center sm:justify-between\"\n      >\n        <div class=\"flex items-center gap-2.5\">\n          <div class=\"border-border bg-background flex size-8 items-center justify-center rounded-lg border shadow-2xs\">\n            <lucide-icon [img]=\"Flame\" class=\"size-4 fill-orange-500/20 text-orange-500\" />\n          </div>\n          <div>\n            <div class=\"flex items-center gap-2\">\n              <span class=\"text-foreground text-sm font-semibold tracking-tight\">{{ saleTitle }}</span>\n              <span ui-badge variant=\"destructive\" class=\"h-5 gap-1 px-1.5 font-mono text-xs uppercase\">\n                <span class=\"bg-background size-1.5 animate-pulse rounded-full\"></span>\n                Live\n              </span>\n            </div>\n            <p class=\"text-muted-foreground text-xs\">Price locks automatically once added to cart.</p>\n          </div>\n        </div>\n\n        <!-- Live Clock Digits -->\n        <div class=\"flex items-center gap-2 self-start sm:self-auto\">\n          <span class=\"text-muted-foreground text-xs font-medium\">Ends in:</span>\n          <div class=\"text-foreground flex items-center gap-1 font-mono text-xs font-semibold\">\n            <span class=\"border-border bg-background rounded border px-1.5 py-0.5 shadow-2xs\"\n              >{{ timeLeft().hours }}h</span\n            >\n            <span>:</span>\n            <span class=\"border-border bg-background rounded border px-1.5 py-0.5 shadow-2xs\"\n              >{{ timeLeft().minutes }}m</span\n            >\n            <span>:</span>\n            <span class=\"border-border bg-background text-destructive rounded border px-1.5 py-0.5 shadow-2xs\"\n              >{{ timeLeft().seconds }}s</span\n            >\n          </div>\n        </div>\n      </div>\n\n      <div class=\"space-y-4 p-4\">\n        <!-- Claim Progress Bar -->\n        <div class=\"space-y-1.5\">\n          <div class=\"flex items-center justify-between text-xs\">\n            <span class=\"text-foreground flex items-center gap-1.5 font-medium\">\n              <lucide-icon [img]=\"Flame\" class=\"text-warning size-3.5\" />\n              <span>{{ claimedPercent }}% Claimed</span>\n            </span>\n            <span class=\"text-destructive font-mono font-medium\">Only {{ itemsLeft }} units remaining in stock</span>\n          </div>\n          <div class=\"bg-muted h-2 w-full overflow-hidden rounded-full\">\n            <div\n              class=\"h-full rounded-full bg-gradient-to-r from-orange-500 to-rose-500 transition-[width] duration-500\"\n              [style.width]=\"claimedPercent + '%'\"\n            ></div>\n          </div>\n        </div>\n\n        <!-- Tier Multi-Buy Cards Grid -->\n        <div class=\"grid grid-cols-1 gap-2.5 sm:grid-cols-3\">\n          @for (tier of tiers; track tier.id) {\n            <button\n              type=\"button\"\n              [class]=\"tierCardClass(tier)\"\n              (click)=\"handleSelectTier(tier)\"\n              [attr.aria-pressed]=\"selectedTierId() === tier.id\"\n            >\n              <!-- Popular ribbon -->\n              @if (tier.popular) {\n                <span\n                  class=\"border-border bg-primary text-primary-foreground absolute -top-2.5 right-3 rounded-full border px-2 py-0.5 text-xs font-semibold shadow-xs\"\n                >\n                  MOST POPULAR\n                </span>\n              }\n\n              <div class=\"space-y-1\">\n                <div class=\"flex items-center justify-between\">\n                  <span class=\"text-foreground text-xs font-semibold\"\n                    >Buy {{ tier.quantity }} {{ tier.quantity > 1 ? 'Units' : 'Unit' }}</span\n                  >\n                  <span ui-badge variant=\"secondary\" class=\"text-success font-mono text-xs font-bold\">\n                    -{{ tier.discountPct }}% OFF\n                  </span>\n                </div>\n                <div class=\"text-muted-foreground text-xs font-medium\">{{ tier.label }}</div>\n              </div>\n\n              <div class=\"border-border/60 mt-3 flex items-center justify-between border-t pt-2 text-xs\">\n                <span class=\"text-muted-foreground\">{{ tier.badgeText }}</span>\n                <div [class]=\"tierCheckClass(tier)\">✓</div>\n              </div>\n            </button>\n          }\n        </div>\n\n        <!-- Footer Guarantee & CTA Banner -->\n        <div\n          class=\"border-border bg-muted/20 flex flex-col gap-2 rounded-lg border p-3 text-xs sm:flex-row sm:items-center sm:justify-between\"\n        >\n          <div class=\"text-muted-foreground flex items-center gap-2\">\n            <lucide-icon [img]=\"ShieldCheck\" class=\"text-success size-4\" />\n            <span>30-day price-match guarantee · Free express 2-day dispatch</span>\n          </div>\n          <button ui-button size=\"sm\" class=\"gap-1.5 text-xs shadow-xs\" (click)=\"claimDeal.emit()\">\n            <lucide-icon [img]=\"Tag\" class=\"size-3.5\" />\n            <span>Apply Tier Discount to Cart</span>\n          </button>\n        </div>\n      </div>\n    </div>\n  `,\n})\nexport class UiFlashSaleOfferBarComponent {\n  protected readonly Flame = Flame\n  protected readonly ShieldCheck = ShieldCheck\n  protected readonly Tag = Tag\n\n  @Input() saleTitle = 'Lightning Deal: Limited Quantity Batch'\n  @Input() endTimeIso?: string\n  @Input() claimedPercent = 78\n  @Input() itemsLeft = 14\n  /** Accepted for Vue/React API parity (the default selection is the popular tier). */\n  @Input() currentTier = 2\n  @Input() tiers: TierOffer[] = DEFAULT_TIERS.map((t) => ({ ...t }))\n  @Input('class') className?: string\n\n  @Output() tierSelect = new EventEmitter<TierOffer>()\n  @Output() claimDeal = new EventEmitter<void>()\n\n  readonly selectedTierId = signal('')\n  readonly timeLeft = signal<TimeLeft>({ hours: '06', minutes: '42', seconds: '19' })\n\n  private defaultEndIso: string | null = null\n  private timer: ReturnType<typeof setInterval> | undefined\n\n  constructor() {\n    inject(DestroyRef).onDestroy(() => clearInterval(this.timer))\n    // Browser only, after the first render (the Vue onMounted / React useEffect).\n    afterNextRender(() => {\n      this.tick()\n      this.timer = setInterval(() => this.tick(), 1000)\n    })\n  }\n\n  ngOnInit(): void {\n    if (!this.selectedTierId()) {\n      this.selectedTierId.set(this.tiers[1]?.id ?? this.tiers[0]?.id ?? '')\n    }\n  }\n\n  get rootClass(): string {\n    return cn('border-border bg-card w-full overflow-hidden rounded-xl border shadow-xs', this.className)\n  }\n\n  tierCardClass(tier: TierOffer): string {\n    return cn(\n      'relative flex flex-col justify-between rounded-lg border p-3 text-left transition-colors',\n      this.selectedTierId() === tier.id\n        ? 'border-primary bg-primary/5 ring-primary ring-1'\n        : 'border-border bg-card hover:border-muted-foreground/40 hover:bg-muted/30',\n    )\n  }\n\n  tierCheckClass(tier: TierOffer): string {\n    return cn(\n      'flex size-4 items-center justify-center rounded-full border text-xs font-bold',\n      this.selectedTierId() === tier.id\n        ? 'border-primary bg-primary text-primary-foreground'\n        : 'border-muted-foreground/40',\n    )\n  }\n\n  targetIso(): string {\n    if (this.endTimeIso) return this.endTimeIso\n    if (!this.defaultEndIso) {\n      this.defaultEndIso = new Date(Date.now() + 6 * 3600 * 1000 + 42 * 60 * 1000 + 19 * 1000).toISOString()\n    }\n    return this.defaultEndIso\n  }\n\n  tick(): void {\n    const target = new Date(this.targetIso()).getTime()\n    const diff = Math.max(0, target - Date.now())\n    const h = Math.floor(diff / (1000 * 60 * 60))\n    const m = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))\n    const s = Math.floor((diff % (1000 * 60)) / 1000)\n    this.timeLeft.set({\n      hours: String(h).padStart(2, '0'),\n      minutes: String(m).padStart(2, '0'),\n      seconds: String(s).padStart(2, '0'),\n    })\n  }\n\n  handleSelectTier(tier: TierOffer): void {\n    this.selectedTierId.set(tier.id)\n    this.tierSelect.emit(tier)\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/flash-sale-offer-bar/flash-sale-offer-bar.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/flash-sale-offer-bar/index.ts",
      "content": "export { UiFlashSaleOfferBarComponent, type TierOffer, type TimeLeft } from './flash-sale-offer-bar.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/flash-sale-offer-bar/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json"
  ],
  "description": "Limited-time flash sale countdown deal bar with stock claim progress meter, tiered multi-buy volume discounts, and 1-click cart activation.",
  "categories": [
    "ecommerce",
    "marketing"
  ]
}