Coming Soon
blockmarketingInteractive 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
$pnpm dlx shadcn@latest add https://uipkge.dev/r/lit/coming-soon.json$npx shadcn@latest add https://uipkge.dev/r/lit/coming-soon.json$yarn dlx shadcn@latest add https://uipkge.dev/r/lit/coming-soon.json$bunx shadcn@latest add https://uipkge.dev/r/lit/coming-soon.jsonManifest:
https://uipkge.dev/r/lit/coming-soon.jsonInstalls to:src/uip/blocks/coming-soon/Variants
Loading interactive previews…
Loading interactive previews…
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
target-iso | string | — | optional |
status-url | string | https://status.uipkge.dev | optional |
product-version | string | v2.0 Beta | optional |
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
Milestoneinterface Milestone {
id: string
title: string
category: string
status: 'completed' | 'in_progress' | 'scheduled'
completionPercent: number
date: string
}FeatureTeaserinterface FeatureTeaser {
id: string
badge: string
title: string
description: string
icon: IconNode
iconName: string
statLabel: string
statValue: string
codeSnippet: string
}HTMLElementTagNameMapinterface HTMLElementTagNameMap {
'uip-coming-soon': UipComingSoon
}
}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>© ${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