Menu

Change language

GitHub repo

Coming Soon

blockmarketing

Interactive launch and maintenance workbench with high-impact countdown, VIP early access waitlist queue allocator, live launch roadmap progress tracker, and feature sneak-peek previews.

Installation

$npx shadcn@latest add https://uipkge.dev/r/lit/coming-soon.json
Manifest:https://uipkge.dev/r/lit/coming-soon.jsonInstalls to:src/uip/blocks/coming-soon/

Variants

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
target-isostring—optional
status-urlstringhttps://status.uipkge.devoptional
product-versionstringv2.0 Betaoptional

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

Milestone
interface Milestone {
  id: string
  title: string
  category: string
  status: 'completed' | 'in_progress' | 'scheduled'
  completionPercent: number
  date: string
}
FeatureTeaser
interface FeatureTeaser {
  id: string
  badge: string
  title: string
  description: string
  icon: IconNode
  iconName: string
  statLabel: string
  statValue: string
  codeSnippet: string
}
HTMLElementTagNameMap
interface HTMLElementTagNameMap {
    'uip-coming-soon': UipComingSoon
  }
}

npm dependencies

Files installed (1)

  • src/uip/blocks/coming-soon/coming-soon.ts27.3 kB
    import { LitElement, css, html } from 'lit'
    import {
      Activity,
      ArrowRight,
      ArrowUpRight,
      Bot,
      Check,
      CheckCircle2,
      Copy,
      Palette,
      Rocket,
      ShieldCheck,
      Terminal,
      Zap,
      type IconNode,
    } from 'lucide'
    import { cn } from '../../lib/utils'
    import { tailwind } from '../../lib/styles'
    import { ThemeController } from '../../lib/theme'
    import { icon } from '../../lib/icon'
    // Side-effect imports: register the composed elements for standalone use
    // (consumers who install only this block still get working primitives).
    import '../../components/badge/badge'
    import '../../components/button/button'
    import '../../components/input/input'
    import type { UipInput } from '../../components/input/input'
    
    function pad(value: number) {
      return String(value).padStart(2, '0')
    }
    
    interface Milestone {
      id: string
      title: string
      category: string
      status: 'completed' | 'in_progress' | 'scheduled'
      completionPercent: number
      date: string
    }
    
    const initialMilestones: Milestone[] = [
      {
        id: 'm1',
        title: 'Dual-Framework Compiler & Monorepo Architecture',
        category: 'Core Infra',
        status: 'completed',
        completionPercent: 100,
        date: 'Aug 15, 2026',
      },
      {
        id: 'm2',
        title: 'Tailwind CSS v4 OKLCH Dynamic Theming Engine',
        category: 'Design System',
        status: 'completed',
        completionPercent: 100,
        date: 'Aug 18, 2026',
      },
      {
        id: 'm3',
        title: 'Zero-Lockin Code Distribution Registry & CLI',
        category: 'DX & Tooling',
        status: 'completed',
        completionPercent: 100,
        date: 'Aug 21, 2026',
      },
      {
        id: 'm4',
        title: 'Multi-Region Edge Registry Cache & Global SLA Testing',
        category: 'Infrastructure',
        status: 'in_progress',
        completionPercent: 88,
        date: 'Target: 3 Days',
      },
      {
        id: 'm5',
        title: 'Public V2.0 Global General Availability Rollout',
        category: 'Release',
        status: 'scheduled',
        completionPercent: 0,
        date: 'Target: 7 Days',
      },
    ]
    
    interface FeatureTeaser {
      id: string
      badge: string
      title: string
      description: string
      icon: IconNode
      iconName: string
      statLabel: string
      statValue: string
      codeSnippet: string
    }
    
    const teasers: FeatureTeaser[] = [
      {
        id: 'agentic',
        badge: 'Flagship AI',
        title: 'Autonomous Multi-Agent Orchestrator',
        description: 'Decompose complex workflow DAGs into parallel subagent execution with real-time vector memory.',
        icon: Bot,
        iconName: 'bot',
        statLabel: 'DAG Execution Speed',
        statValue: '120ms P99',
        codeSnippet: 'npx shadcn-vue@latest add @uipkge/ai-agent-orchestrator',
      },
      {
        id: 'design',
        badge: 'Design Engineering',
        title: 'Tailwind v4 OKLCH Fluid Theme Tokens',
        description:
          'Perceptually uniform color spaces with automatic high-contrast dark mode switching & zero CSS bloat.',
        icon: Palette,
        iconName: 'palette',
        statLabel: 'Bundle Size Overhead',
        statValue: '0.00 kB',
        codeSnippet: 'npx shadcn-vue@latest add @uipkge/theme-customize',
      },
      {
        id: 'edge',
        badge: 'Ultra Low Latency',
        title: 'Cloudflare Workers Multi-Region Cache',
        description: 'Global component manifest resolution with smart geo-routing and edge-cached JSON trees.',
        icon: Zap,
        iconName: 'zap',
        statLabel: 'Global Median Latency',
        statValue: '8.4ms',
        codeSnippet: 'curl -s https://uipkge.dev/r/vue/init.json',
      },
    ]
    
    // Card chrome follows the registry Card verbatim (see the login-01 block:
    // surfaces stay plain divs so per-card classes like the zinc terminal paint
    // land on the painted box). Defaults resolved from cardVariants + the React
    // part classes; per-card className merged the way cn() would.
    const countdownCardClass =
      'bg-card/70 text-card-foreground rounded-xl border border-border group hover:border-primary/40 relative overflow-hidden p-5 text-center shadow-xs backdrop-blur-xs transition-colors duration-150'
    const plainCardClass =
      'bg-card text-card-foreground rounded-xl border border-border overflow-hidden shadow-sm transition-colors duration-150'
    
    /**
     * <uip-coming-soon> — the registry ComingSoon block as a web component.
     *
     * Class strings are React's ComingSoon verbatim. React state (useState)
     * becomes @state fields; the 1s countdown interval starts in
     * `connectedCallback` (recomputed when `target-iso` changes) and is cleared
     * in `disconnectedCallback`, as are the copy-label timeouts. The waitlist
     * form is fully internal like React (no callbacks).
     */
    export class UipComingSoon extends LitElement {
      static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }
      // :host display can't be a utility class (the host has no template of its own).
      static styles = [tailwind, css`:host { display: block; }`]
    
      static properties = {
        targetIso: { attribute: 'target-iso' },
        statusUrl: { attribute: 'status-url' },
        productVersion: { attribute: 'product-version' },
        remaining: { state: true },
        activeTeaserIndex: { state: true },
        email: { state: true },
        audience: { state: true },
        submitted: { state: true },
        queuePosition: { state: true },
        copiedReferral: { state: true },
        copiedCli: { state: true },
      }
    
      targetIso = ''
      statusUrl = 'https://status.uipkge.dev'
      productVersion = 'v2.0 Beta'
      private remaining = { days: '00', hours: '00', minutes: '00', seconds: '00' }
      private activeTeaserIndex = 0
      private email = ''
      private audience: 'frontend' | 'architect' | 'founder' = 'frontend'
      private submitted = false
      private queuePosition: number | null = null
      private copiedReferral = false
      private copiedCli = false
      private deadline = 0
      private timer?: number
      private copyTimer?: number
    
      constructor() {
        super()
        new ThemeController(this)
      }
    
      connectedCallback() {
        super.connectedCallback()
        this.setAttribute('data-uipkge', '')
        this.setAttribute('data-slot', 'coming-soon')
        this.resetDeadline()
        this.tick()
        this.timer = window.setInterval(() => this.tick(), 1000)
      }
    
      disconnectedCallback() {
        super.disconnectedCallback()
        if (this.timer) window.clearInterval(this.timer)
        if (this.copyTimer) window.clearTimeout(this.copyTimer)
      }
    
      protected updated(changed: Map<string, unknown>) {
        if (changed.has('targetIso')) this.resetDeadline()
      }
    
      private resetDeadline() {
        this.deadline = this.targetIso ? new Date(this.targetIso).getTime() : Date.now() + 7 * 24 * 60 * 60 * 1000
      }
    
      private tick() {
        const diff = Math.max(0, this.deadline - Date.now())
        this.remaining = {
          days: pad(Math.floor(diff / 86_400_000)),
          hours: pad(Math.floor((diff % 86_400_000) / 3_600_000)),
          minutes: pad(Math.floor((diff % 3_600_000) / 60_000)),
          seconds: pad(Math.floor((diff % 60_000) / 1000)),
        }
      }
    
      private flashCopy(which: 'referral' | 'cli') {
        if (which === 'referral') this.copiedReferral = true
        else this.copiedCli = true
        if (this.copyTimer) window.clearTimeout(this.copyTimer)
        this.copyTimer = window.setTimeout(() => {
          this.copiedReferral = false
          this.copiedCli = false
        }, 2000)
      }
    
      private copyText(text: string, which: 'referral' | 'cli') {
        try {
          navigator.clipboard?.writeText(text)?.catch(() => {})
        } catch {
          // Clipboard unavailable (insecure context): still flash the label.
        }
        this.flashCopy(which)
      }
    
      private handleWaitlistSubmit(e: Event) {
        e.preventDefault()
        if (!this.email) return
        // Simulated VIP queue calculation
        this.queuePosition = Math.floor(Math.random() * 80) + 120
        this.submitted = true
      }
    
      private progressTrack(value: number, trackClass: string) {
        const clamped = Math.min(100, Math.max(0, value))
        return html`<div
          data-uipkge=""
          data-slot="progress"
          role="progressbar"
          aria-valuemin="0"
          aria-valuemax="100"
          aria-valuenow=${clamped}
          data-state=${clamped >= 100 ? 'complete' : 'loading'}
          data-value=${clamped}
          data-max="100"
          class=${trackClass}
        >
          <div
            data-uipkge=""
            data-slot="progress-indicator"
            data-state=${clamped >= 100 ? 'complete' : 'loading'}
            data-value=${clamped}
            data-max="100"
            class="bg-primary h-full w-full flex-1 transition-transform duration-500 ease-out motion-reduce:transition-none"
            style=${`transform: translateX(-${100 - clamped}%)`}
          ></div>
        </div>`
      }
    
      render() {
        const units = [
          { label: 'Days', value: this.remaining.days },
          { label: 'Hours', value: this.remaining.hours },
          { label: 'Minutes', value: this.remaining.minutes },
          { label: 'Seconds', value: this.remaining.seconds },
        ]
        const overallReadiness = Math.round(
          initialMilestones.reduce((acc, m) => acc + m.completionPercent, 0) / initialMilestones.length,
        )
        const active = teasers[this.activeTeaserIndex]
        const year = new Date().getFullYear()
        return html`
          <section
            data-slot="coming-soon"
            class="bg-background relative flex min-h-screen flex-col justify-between overflow-hidden px-4 py-16 sm:px-6 lg:px-8"
          >
            <div
              class="border-border/70 mx-auto flex w-full max-w-6xl flex-col items-center justify-between gap-4 border-b pb-12 sm:flex-row"
            >
              <div class="flex items-center gap-3">
                <div
                  class="bg-primary text-primary-foreground flex size-9 items-center justify-center rounded-xl font-bold shadow-xs"
                >
                  ${icon(Rocket, 'rocket', 'size-5')}
                </div>
                <div>
                  <div class="flex items-center gap-2">
                    <span class="text-foreground text-sm font-semibold tracking-tight">UIPKGE Engine</span>
                    <uip-badge variant="outline" class="border-primary/30 text-primary px-1.5 py-0.5 font-mono text-xs">
                      ${this.productVersion}
                    </uip-badge>
                  </div>
                  <p class="text-muted-foreground text-xs">Dual-framework registry & visual workbench suite</p>
                </div>
              </div>
    
              <div class="bg-muted/40 border-border flex items-center gap-3 rounded-full border px-3.5 py-1.5">
                <div class="bg-success flex size-2 animate-pulse rounded-full"></div>
                <span class="text-muted-foreground font-mono text-xs">
                  Staging Infrastructure: <strong class="text-foreground font-semibold">99.99% Operational</strong>
                </span>
                <div
                  data-uipkge=""
                  data-slot="separator"
                  data-orientation="vertical"
                  role="none"
                  class="bg-border shrink-0 w-px h-3.5"
                ></div>
                <a
                  href=${this.statusUrl}
                  target="_blank"
                  rel="noreferrer"
                  class="text-primary inline-flex items-center gap-1 font-mono text-xs hover:underline"
                >
                  Live Status ${icon(ArrowUpRight, 'arrow-up-right', 'size-3')}
                </a>
              </div>
            </div>
    
            <div class="mx-auto my-auto w-full max-w-6xl space-y-16 py-10">
              <div class="mx-auto max-w-3xl space-y-4 text-center">
                <uip-badge variant="secondary" class="gap-1.5 px-3 py-1 font-mono text-xs shadow-xs">
                  ${icon(Rocket, 'rocket', 'text-primary size-3.5')} V2.0 General Availability Launch Matrix
                </uip-badge>
                <h1 class="text-foreground text-4xl leading-[1.1] font-bold tracking-tight sm:text-5xl lg:text-6xl">
                  Production-grade design engineering workbenches.
                </h1>
                <p class="text-muted-foreground mx-auto max-w-2xl text-base leading-relaxed sm:text-lg">
                  We are deploying 281+ production-grade primitives and composed workbenches with zero package dependencies.
                  Own your source code with complete architectural freedom.
                </p>
              </div>
    
              <div class="mx-auto grid max-w-2xl grid-cols-2 gap-4 sm:grid-cols-4">
                ${units.map(
                  (unit) => html`<div data-uipkge="" data-slot="card" class=${countdownCardClass}>
                    <div class="text-foreground font-mono text-4xl font-bold tracking-tight tabular-nums sm:text-5xl">
                      ${unit.value}
                    </div>
                    <div class="text-muted-foreground mt-2 font-mono text-xs font-medium tracking-widest uppercase">
                      ${unit.label}
                    </div>
                    <div class="bg-primary/20 group-hover:bg-primary absolute right-0 bottom-0 left-0 h-0.5 transition-colors"></div>
                  </div>`,
                )}
              </div>
    
              <div class="mx-auto w-full max-w-xl">
                <div data-uipkge="" data-slot="card" class=${plainCardClass}>
                  <div
                    data-uipkge=""
                    data-slot="card-header"
                    class="grid auto-rows-min grid-cols-[minmax(0,1fr)] grid-rows-[auto_auto] items-start gap-1.5 p-6 pb-4 has-data-[slot=card-action]:grid-cols-[minmax(0,1fr)_auto]"
                  >
                    <div class="flex items-center justify-between">
                      <h3
                        data-uipkge=""
                        data-slot="card-title"
                        class="leading-none tracking-tight flex items-center gap-2 text-base font-semibold"
                      >
                        ${icon(ShieldCheck, 'shield-check', 'text-primary size-4')} Early Access Allocation
                      </h3>
                      <uip-badge variant="outline" class="border-success/20 bg-success/10 text-success font-mono text-xs">
                        Batch #2 Opening Soon
                      </uip-badge>
                    </div>
                    <p data-uipkge="" data-slot="card-description" class="text-muted-foreground text-xs">
                      Request priority invite access. Get instant sandbox access to all 281+ blocks before general public
                      launch.
                    </p>
                  </div>
    
                  <div data-uipkge="" data-slot="card-content" class="space-y-6 p-6 pt-0">
                    ${!this.submitted
                      ? html`<form @submit=${this.handleWaitlistSubmit} class="space-y-4">
                          <div class="space-y-1.5">
                            <label class="text-foreground text-xs font-medium">Your Primary Engineering Focus</label>
                            <div class="grid grid-cols-3 gap-2">
                              ${[
                                { id: 'frontend', label: 'Frontend / UI' },
                                { id: 'architect', label: 'Tech Lead / Arch' },
                                { id: 'founder', label: 'CTO / Founder' },
                              ].map(
                                (r) => html`<button
                                  type="button"
                                  class=${cn(
                                    'rounded-lg border px-2.5 py-1.5 text-center text-xs font-medium transition-colors',
                                    this.audience === r.id
                                      ? 'border-primary bg-primary/10 text-primary font-semibold shadow-xs'
                                      : 'border-border bg-background text-muted-foreground hover:text-foreground',
                                  )}
                                  @click=${() => {
                                    this.audience = r.id as 'frontend' | 'architect' | 'founder'
                                  }}
                                >
                                  ${r.label}
                                </button>`,
                              )}
                            </div>
                          </div>
    
                          <div class="flex flex-col gap-2 sm:flex-row">
                            <div class="relative flex-1">
                              <uip-input
                                type="email"
                                placeholder="[email protected]"
                                .value=${this.email}
                                required
                                class="h-10 font-mono text-xs"
                                @input=${(e: Event) => {
                                  this.email = (e.target as UipInput).value
                                }}
                              ></uip-input>
                            </div>
                            <uip-button type="submit" class="h-10 gap-1.5 px-5 text-xs font-semibold">
                              <span>Reserve Spot</span>
                              ${icon(ArrowRight, 'arrow-right', 'size-3.5')}
                            </uip-button>
                          </div>
                          <p class="text-muted-foreground text-center text-xs">
                            No spam. You will only receive your single cryptographic invite token.
                          </p>
                        </form>`
                      : html`<div class="bg-muted/40 border-border space-y-4 rounded-xl border p-4 text-center">
                          <div class="bg-success/10 text-success mx-auto flex size-10 items-center justify-center rounded-full">
                            ${icon(Check, 'check', 'size-5')}
                          </div>
                          <div class="space-y-1">
                            <p class="text-foreground text-sm font-semibold">You are #${this.queuePosition} in queue!</p>
                            <p class="text-muted-foreground text-xs">
                              Invitation tokens for Batch #2 will be delivered to
                              <strong class="text-foreground font-mono">${this.email}</strong>.
                            </p>
                          </div>
    
                          <div
                            class="border-border flex flex-col items-center justify-between gap-3 border-t pt-2 sm:flex-row"
                          >
                            <span class="text-muted-foreground font-mono text-xs">
                              Move up 5 spots per teammate invite:
                            </span>
                            <uip-button
                              variant="outline"
                              size="sm"
                              class="h-8 gap-1.5 font-mono text-xs"
                              @click=${() =>
                                this.copyText(`https://uipkge.dev/invite?ref=queue_${this.queuePosition}`, 'referral')}
                            >
                              ${this.copiedReferral
                                ? icon(Check, 'check', 'text-success size-3')
                                : icon(Copy, 'copy', 'size-3')}
                              <span>${this.copiedReferral ? 'Copied Link' : 'Copy Invite Link'}</span>
                            </uip-button>
                          </div>
                        </div>`}
                  </div>
                </div>
              </div>
    
              <div class="space-y-6">
                <div class="flex flex-col justify-between gap-4 sm:flex-row sm:items-center">
                  <div>
                    <h2 class="text-foreground flex items-center gap-2 text-lg font-semibold tracking-tight">
                      ${icon(Zap, 'zap', 'text-primary size-4')} Incoming Flagship Capabilities
                    </h2>
                    <p class="text-muted-foreground text-xs">Sneak peek preview of the upcoming architecture release</p>
                  </div>
    
                  <div class="bg-muted/40 border-border flex items-center gap-1.5 rounded-lg border p-1">
                    ${teasers.map(
                      (t, idx) => html`<button
                        type="button"
                        class=${cn(
                          'rounded-md px-3 py-1 text-xs font-medium transition-colors',
                          this.activeTeaserIndex === idx
                            ? 'bg-background text-foreground font-semibold shadow-xs'
                            : 'text-muted-foreground hover:text-foreground',
                        )}
                        @click=${() => {
                          this.activeTeaserIndex = idx
                        }}
                      >
                        ${t.badge}
                      </button>`,
                    )}
                  </div>
                </div>
    
                <div data-uipkge="" data-slot="card" class=${plainCardClass}>
                  <div class="divide-border grid grid-cols-1 divide-y lg:grid-cols-12 lg:divide-x lg:divide-y-0">
                    <div class="space-y-4 p-6 sm:p-8 lg:col-span-7">
                      <div class="flex items-center gap-2.5">
                        <div class="bg-primary/10 text-primary rounded-lg p-2">${icon(active.icon, active.iconName, 'size-5')}</div>
                        <uip-badge variant="secondary" class="font-mono text-xs">${active.badge}</uip-badge>
                      </div>
    
                      <h3 class="text-foreground text-xl font-bold tracking-tight">${active.title}</h3>
                      <p class="text-muted-foreground text-xs leading-relaxed sm:text-sm">${active.description}</p>
    
                      <div class="flex items-center gap-6 pt-2">
                        <div>
                          <p class="text-muted-foreground font-mono text-xs">${active.statLabel}</p>
                          <p class="text-foreground mt-0.5 font-mono text-xl font-bold">${active.statValue}</p>
                        </div>
                        <div
                          data-uipkge=""
                          data-slot="separator"
                          data-orientation="vertical"
                          role="none"
                          class="bg-border shrink-0 w-px h-8"
                        ></div>
                        <div>
                          <p class="text-muted-foreground font-mono text-xs">Framework Support</p>
                          <p class="text-success mt-1 text-xs font-semibold">Vue 3.5 & React 19 Parity</p>
                        </div>
                      </div>
                    </div>
    
                    <div class="bg-muted/20 flex flex-col justify-between space-y-4 p-6 lg:col-span-5">
                      <div class="space-y-2">
                        <div class="flex items-center justify-between">
                          <span class="text-muted-foreground flex items-center gap-1.5 font-mono text-xs font-medium">
                            ${icon(Terminal, 'terminal', 'size-3.5')} Direct CLI Command
                          </span>
                          <uip-button
                            variant="ghost"
                            size="sm"
                            class="h-7 gap-1.5 px-2 font-mono text-xs"
                            @click=${() => this.copyText(active.codeSnippet, 'cli')}
                          >
                            ${this.copiedCli ? icon(Check, 'check', 'text-success size-3') : icon(Copy, 'copy', 'size-3')}
                            <span>${this.copiedCli ? 'Copied' : 'Copy'}</span>
                          </uip-button>
                        </div>
                        <div
                          class="bg-background border-border text-foreground selection:bg-primary/20 overflow-x-auto rounded-lg border p-3 font-mono text-xs"
                        >
                          <code>${active.codeSnippet}</code>
                        </div>
                      </div>
    
                      <div
                        class="border-border/80 bg-background/50 text-muted-foreground flex items-center gap-2 rounded-lg border p-3 text-xs"
                      >
                        ${icon(CheckCircle2, 'circle-check-big', 'text-success size-3.5 shrink-0')}
                        <span>Transitive dependencies, OKLCH styles & TS types included.</span>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
    
              <div class="border-border/70 space-y-6 border-t pt-4">
                <div class="flex flex-col justify-between gap-4 sm:flex-row sm:items-center">
                  <div>
                    <h2 class="text-foreground flex items-center gap-2 text-lg font-semibold tracking-tight">
                      ${icon(Activity, 'activity', 'text-primary size-4')} V2.0 Launch Milestones & Engineering Progress
                    </h2>
                    <p class="text-muted-foreground text-xs">Transparent real-time build and release status</p>
                  </div>
                  <div class="flex items-center gap-3">
                    <span class="text-foreground font-mono text-xs font-semibold">
                      Overall Readiness: ${overallReadiness}%
                    </span>
                    ${this.progressTrack(overallReadiness, 'bg-primary/20 relative h-2 w-28 overflow-hidden rounded-full')}
                  </div>
                </div>
    
                <div class="grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-3">
                  ${initialMilestones.map(
                    (m) => html`<div
                      class="border-border bg-card hover:border-primary/30 flex flex-col justify-between space-y-3 rounded-xl border p-4 shadow-xs transition-colors"
                    >
                      <div class="flex items-start justify-between gap-2">
                        <uip-badge
                          variant="outline"
                          class=${cn(
                            'px-2 py-0.5 font-mono text-xs',
                            m.status === 'completed' && 'border-success/20 bg-success/10 text-success',
                            m.status === 'in_progress' && 'border-warning/20 bg-warning/10 text-warning',
                            m.status === 'scheduled' && 'bg-muted text-muted-foreground border-border',
                          )}
                        >
                          ${m.status === 'completed' ? 'Completed' : m.status === 'in_progress' ? 'In Progress' : 'Scheduled'}
                        </uip-badge>
                        <span class="text-muted-foreground font-mono text-xs">${m.date}</span>
                      </div>
    
                      <div>
                        <p class="text-foreground text-xs leading-snug font-semibold">${m.title}</p>
                        <p class="text-muted-foreground mt-1 font-mono text-xs">${m.category}</p>
                      </div>
    
                      <div class="space-y-1 pt-1">
                        <div class="text-muted-foreground flex justify-between font-mono text-xs">
                          <span>Progress</span>
                          <span>${m.completionPercent}%</span>
                        </div>
                        ${this.progressTrack(m.completionPercent, 'bg-primary/20 relative h-1.5 w-full overflow-hidden rounded-full')}
                      </div>
                    </div>`,
                  )}
                </div>
              </div>
            </div>
    
            <footer
              class="border-border text-muted-foreground mx-auto flex w-full max-w-6xl flex-col items-center justify-between gap-4 border-t pt-8 text-xs sm:flex-row"
            >
              <div class="flex items-center gap-2">
                <span>&copy; ${year} UIPKGE. Open source under MIT License.</span>
              </div>
              <div class="flex items-center gap-4">
                <a
                  href="https://github.com/uday-a/uipkge-registry"
                  target="_blank"
                  rel="noreferrer"
                  class="hover:text-foreground transition-colors"
                >
                  GitHub
                </a>
                <div
                  data-uipkge=""
                  data-slot="separator"
                  data-orientation="vertical"
                  role="none"
                  class="bg-border shrink-0 w-px h-3"
                ></div>
                <a href="https://uipkge.dev" target="_blank" rel="noreferrer" class="hover:text-foreground transition-colors">
                  Registry Docs
                </a>
                <div
                  data-uipkge=""
                  data-slot="separator"
                  data-orientation="vertical"
                  role="none"
                  class="bg-border shrink-0 w-px h-3"
                ></div>
                <a href=${this.statusUrl} target="_blank" rel="noreferrer" class="hover:text-foreground transition-colors">
                  Status Page
                </a>
              </div>
            </footer>
          </section>
        `
      }
    }
    
    customElements.get('uip-coming-soon') || customElements.define('uip-coming-soon', UipComingSoon)
    
    declare global {
      interface HTMLElementTagNameMap {
        'uip-coming-soon': UipComingSoon
      }
    }
    

Raw manifest:https://uipkge.dev/r/lit/coming-soon.json