{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "sticky-cta-reading-progress",
  "title": "Sticky CTA — Reading Progress",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/sticky-cta-reading-progress/sticky-cta-reading-progress.component.ts",
      "content": "// Sticky bottom bar that doubles as a reading-progress indicator, revealing partway down\n// a long page and reporting how much of it remains. Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, DestroyRef, Input, OnInit, inject, signal } from '@angular/core'\nimport { ArrowRight, LucideAngularModule, X } from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport { UiProgressComponent } from '@/ui/progress/progress.component'\nimport { UiSeparatorComponent } from '@/ui/separator/separator.component'\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sticky-cta-reading-progress, [ui-sticky-cta-reading-progress]',\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, UiProgressComponent, UiSeparatorComponent],\n  template: `\n    <div\n      data-slot=\"sticky-cta-reading-progress\"\n      [class]=\"rootClass\"\n      role=\"region\"\n      aria-label=\"Reading progress and trial offer\"\n      [attr.aria-hidden]=\"!visible()\"\n    >\n      <ui-progress [value]=\"progress()\" class=\"h-0.5 rounded-none\" aria-label=\"Reading progress\" />\n\n      <div class=\"mx-auto flex max-w-5xl flex-wrap items-center gap-x-5 gap-y-2 px-6 py-3\">\n        <span ui-badge variant=\"secondary\" class=\"shrink-0 tabular-nums\">{{ progress() }}% read</span>\n\n        <p class=\"min-w-0 grow text-sm\">\n          <span class=\"font-medium\">Finished the technical detail?</span>\n          <span class=\"text-muted-foreground\"> The trial runs on your real warehouse, read-only.</span>\n        </p>\n\n        <div class=\"flex shrink-0 items-center gap-2\">\n          <button ui-button size=\"sm\">\n            Start free\n            <lucide-icon [img]=\"ArrowRight\" class=\"ml-1.5 size-3.5\" />\n          </button>\n          <ui-separator orientation=\"vertical\" class=\"hidden h-5 sm:block\" />\n          <button ui-button size=\"icon\" variant=\"ghost\" class=\"size-7\" aria-label=\"Dismiss\" (click)=\"dismiss()\">\n            <lucide-icon [img]=\"X\" class=\"size-3.5\" />\n          </button>\n        </div>\n      </div>\n    </div>\n  `,\n})\nexport class UiStickyCtaReadingProgressComponent implements OnInit {\n  protected readonly ArrowRight = ArrowRight\n  protected readonly X = X\n\n  @Input() revealAt = 15\n  @Input('class') className?: string\n\n  // Both start at rest so server and first client paint agree; the scroll handler\n  // is the only thing that reveals the bar or moves the progress.\n  readonly progress = signal(0)\n  readonly visible = signal(false)\n  private dismissed = false\n  private onScroll = (): void => {\n    if (typeof window === 'undefined' || typeof document === 'undefined') return\n    const scrollable = document.documentElement.scrollHeight - window.innerHeight\n    const percent = scrollable > 0 ? Math.min(Math.round((window.scrollY / scrollable) * 100), 100) : 0\n    this.progress.set(percent)\n    this.visible.set(!this.dismissed && percent >= this.revealAt)\n  }\n\n  constructor() {\n    const destroyRef = inject(DestroyRef)\n    destroyRef.onDestroy(() => {\n      if (typeof window !== 'undefined') {\n        window.removeEventListener('scroll', this.onScroll)\n        window.removeEventListener('resize', this.onScroll)\n      }\n    })\n  }\n\n  ngOnInit(): void {\n    if (typeof window === 'undefined') return\n    this.onScroll()\n    window.addEventListener('scroll', this.onScroll, { passive: true })\n    window.addEventListener('resize', this.onScroll)\n  }\n\n  dismiss(): void {\n    this.dismissed = true\n    this.visible.set(false)\n  }\n\n  get rootClass(): string {\n    return cn(\n      'border-border bg-card/95 fixed inset-x-0 bottom-0 z-50 border-t backdrop-blur transition duration-200',\n      this.visible() ? 'translate-y-0 opacity-100' : 'pointer-events-none invisible translate-y-full opacity-0',\n      this.className,\n    )\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/sticky-cta-reading-progress/sticky-cta-reading-progress.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/sticky-cta-reading-progress/index.ts",
      "content": "export { UiStickyCtaReadingProgressComponent } from './sticky-cta-reading-progress.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/sticky-cta-reading-progress/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/progress.json",
    "https://uipkge.dev/r/angular/separator.json"
  ],
  "description": "Sticky bottom bar that doubles as a reading-progress indicator, revealing partway down a long page and reporting how much of it remains.",
  "categories": [
    "marketing"
  ],
  "deprecated": true,
  "replacedBy": "sticky-cta"
}