{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "flash-sale-offer-bar",
  "title": "Flash Sale Offer Bar",
  "type": "registry:block",
  "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"
  ],
  "files": [
    {
      "path": "packages/elements-lit/src/blocks/flash-sale-offer-bar/flash-sale-offer-bar.ts",
      "content": "import { LitElement, css, html, nothing } from 'lit'\nimport { Flame, ShieldCheck, Tag } from 'lucide'\nimport { tailwind } from '../../lib/styles'\nimport { ThemeController } from '../../lib/theme'\nimport { icon } from '../../lib/icon'\n// Side-effect imports: register the composed elements for standalone use\n// (consumers who install only this block still get working primitives).\nimport '../../components/badge/badge'\nimport '../../components/button/button'\n\nexport interface TierOffer {\n  id: string\n  quantity: number\n  discountPct: number\n  label: string\n  badgeText?: string\n  popular?: boolean\n}\n\nconst defaultTiers: 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/**\n * <uip-flash-sale-offer-bar> — the registry FlashSaleOfferBar block as a web component.\n *\n * Class strings and tier data are React's verbatim. React state becomes\n * { state: true } fields; the 1s countdown interval is cleared in\n * disconnectedCallback. `tiers` is a property (attribute: false — set it from\n * JS). `onSelectTier` (tier) / `onClaimDeal` become `select-tier` /\n * `claim-deal` CustomEvents (bubbles, composed; tier in `detail`). React's\n * `currentTier` prop is declared but never read by the source, so it is not\n * ported.\n */\nexport class UipFlashSaleOfferBar extends LitElement {\n  static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }\n  // :host display can't be a utility class (the host has no template of its own).\n  static styles = [tailwind, css`:host { display: block; }`]\n\n  static properties = {\n    saleTitle: { attribute: 'sale-title' },\n    endTimeIso: { attribute: 'end-time-iso' },\n    claimedPercent: { type: Number, attribute: 'claimed-percent' },\n    itemsLeft: { type: Number, attribute: 'items-left' },\n    tiers: { attribute: false },\n    selectedTierId: { state: true },\n    hours: { state: true },\n    minutes: { state: true },\n    seconds: { state: true },\n  }\n\n  saleTitle = 'Lightning Deal: Limited Quantity Batch'\n  endTimeIso?: string\n  claimedPercent = 78\n  itemsLeft = 14\n  tiers: TierOffer[] = defaultTiers\n\n  private selectedTierId = ''\n  private hours = '06'\n  private minutes = '42'\n  private seconds = '19'\n  private timer?: number\n\n  constructor() {\n    super()\n    new ThemeController(this)\n  }\n\n  connectedCallback() {\n    super.connectedCallback()\n    this.setAttribute('data-uipkge', '')\n    this.setAttribute('data-slot', 'flash-sale-offer-bar')\n    const list = this.tiers ?? []\n    this.selectedTierId = list[1]?.id || list[0]?.id || ''\n    this.updateCountdown()\n    this.timer = window.setInterval(() => this.updateCountdown(), 1000)\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback()\n    if (this.timer) window.clearInterval(this.timer)\n  }\n\n  willUpdate(changed: Map<string, unknown>) {\n    if (changed.has('tiers')) {\n      const list = this.tiers ?? []\n      if (!list.some((t) => t.id === this.selectedTierId)) {\n        this.selectedTierId = list[1]?.id || list[0]?.id || ''\n      }\n    }\n  }\n\n  private get targetTime(): string {\n    if (this.endTimeIso) return this.endTimeIso\n    return new Date(Date.now() + 6 * 3600 * 1000 + 42 * 60 * 1000 + 19 * 1000).toISOString()\n  }\n\n  private updateCountdown() {\n    const target = new Date(this.targetTime).getTime()\n    const now = Date.now()\n    const diff = Math.max(0, target - 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.hours = String(h).padStart(2, '0')\n    this.minutes = String(m).padStart(2, '0')\n    this.seconds = String(s).padStart(2, '0')\n  }\n\n  private handleSelect(tier: TierOffer) {\n    this.selectedTierId = tier.id\n    this.dispatchEvent(new CustomEvent('select-tier', { detail: { ...tier }, bubbles: true, composed: true }))\n  }\n\n  render() {\n    const tiers = this.tiers ?? []\n    return html`\n      <div data-slot=\"flash-sale-offer-bar\" class=\"border-border bg-card w-full overflow-hidden rounded-xl border shadow-xs\">\n        <div 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          <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              ${icon(Flame, 'flame', '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\">${this.saleTitle}</span>\n                <uip-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                </uip-badge>\n              </div>\n              <p class=\"text-muted-foreground text-xs\">Price locks automatically once added to cart.</p>\n            </div>\n          </div>\n\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\">${this.hours}h</span>\n              <span>:</span>\n              <span class=\"border-border bg-background rounded border px-1.5 py-0.5 shadow-2xs\">${this.minutes}m</span>\n              <span>:</span>\n              <span class=\"border-border bg-background text-destructive rounded border px-1.5 py-0.5 shadow-2xs\"\n                >${this.seconds}s</span\n              >\n            </div>\n          </div>\n        </div>\n\n        <div class=\"space-y-4 p-4\">\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                ${icon(Flame, 'flame', 'text-warning size-3.5')}\n                <span>${this.claimedPercent}% Claimed</span>\n              </span>\n              <span class=\"text-destructive font-mono font-medium\">Only ${this.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:${this.claimedPercent}%\"\n              ></div>\n            </div>\n          </div>\n\n          <div class=\"grid grid-cols-1 gap-2.5 sm:grid-cols-3\">\n            ${tiers.map(\n              (tier) => html`<button\n                type=\"button\"\n                class=${`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                @click=${() => this.handleSelect(tier)}\n              >\n                ${tier.popular\n                  ? html`<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                  : nothing}\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'}\n                    </span>\n                    <uip-badge variant=\"secondary\" class=\"text-success font-mono text-xs font-bold\">\n                      -${tier.discountPct}% OFF\n                    </uip-badge>\n                  </div>\n                  <div class=\"text-muted-foreground text-xs font-medium\">${tier.label}</div>\n                </div>\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\n                    class=${`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                  </div>\n                </div>\n              </button>`,\n            )}\n          </div>\n\n          <div 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            <div class=\"text-muted-foreground flex items-center gap-2\">\n              ${icon(ShieldCheck, 'shield-check', 'text-success size-4')}\n              <span>30-day price-match guarantee · Free express 2-day dispatch</span>\n            </div>\n            <uip-button\n              size=\"sm\"\n              class=\"gap-1.5 text-xs shadow-xs\"\n              @click=${() => this.dispatchEvent(new CustomEvent('claim-deal', { bubbles: true, composed: true }))}\n            >\n              ${icon(Tag, 'tag', 'size-3.5')}\n              <span>Apply Tier Discount to Cart</span>\n            </uip-button>\n          </div>\n        </div>\n      </div>\n    `\n  }\n}\n\ncustomElements.get('uip-flash-sale-offer-bar') || customElements.define('uip-flash-sale-offer-bar', UipFlashSaleOfferBar)\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'uip-flash-sale-offer-bar': UipFlashSaleOfferBar\n  }\n}\n",
      "type": "registry:file",
      "target": "src/uip/blocks/flash-sale-offer-bar/flash-sale-offer-bar.ts"
    }
  ],
  "dependencies": [
    "lit",
    "lucide"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/lit/badge.json",
    "https://uipkge.dev/r/lit/button.json",
    "https://uipkge.dev/r/lit/lit-core.json"
  ]
}
