{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "seo-audit-checklist",
  "title": "Seo Audit Checklist",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/seo-audit-checklist/seo-audit-checklist.component.ts",
      "content": "// Ahrefs and SEMrush style on-page SEO analyzer: meta tag auditor, Google SERP\n// snippet preview simulator, heading hierarchy validator, and Core Web Vitals\n// checklist. Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'\nimport {\n  AlertCircle,\n  AlertTriangle,\n  Check,\n  CheckCircle2,\n  Code2,\n  Copy,\n  Eye,\n  FileDown,\n  FileText,\n  Globe,\n  Heading1,\n  Info,\n  Lock,\n  LucideAngularModule,\n  Monitor,\n  RefreshCw,\n  Search,\n  Share2,\n  ShieldCheck,\n  Smartphone,\n  Zap,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport {\n  UiAccordionComponent,\n  UiAccordionContentComponent,\n  UiAccordionItemComponent,\n  UiAccordionTriggerComponent,\n} from '@/ui/accordion/accordion.component'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport {\n  UiCardComponent,\n  UiCardContentComponent,\n  UiCardDescriptionComponent,\n  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport { UiInputComponent } from '@/ui/input/input.component'\nimport { UiProgressComponent } from '@/ui/progress/progress.component'\nimport { UiSeparatorComponent } from '@/ui/separator/separator.component'\n\nexport type CheckStatus = 'pass' | 'warning' | 'fail' | 'info'\n\nexport interface SeoCheckItem {\n  id: string\n  title: string\n  description: string\n  status: CheckStatus\n  value?: string\n  recommendation?: string\n  tag?: string\n}\n\nexport interface SeoCategory {\n  id: string\n  name: string\n  icon: 'meta' | 'headings' | 'opengraph' | 'technical'\n  score: number\n  checks: SeoCheckItem[]\n}\n\nexport interface CoreVitalMetric {\n  name: string\n  label: string\n  value: string\n  status: 'good' | 'needs-improvement' | 'poor'\n  target: string\n}\n\nconst defaultVitals: CoreVitalMetric[] = [\n  { name: 'LCP', label: 'Largest Contentful Paint', value: '1.2s', status: 'good', target: '≤ 2.5s' },\n  { name: 'FID', label: 'First Input Delay', value: '12ms', status: 'good', target: '≤ 100ms' },\n  { name: 'CLS', label: 'Cumulative Layout Shift', value: '0.01', status: 'good', target: '≤ 0.1' },\n]\n\nconst defaultCategories: SeoCategory[] = [\n  {\n    id: 'meta-tags',\n    name: 'Title & Meta Description',\n    icon: 'meta',\n    score: 100,\n    checks: [\n      {\n        id: 'title-length',\n        title: 'Title Tag Length & Structure',\n        description: 'Page title is 58 characters (optimal range: 50–60 characters). Fully visible in 98% of SERPs.',\n        status: 'pass',\n        value: 'UIPKGE — Dual-Framework UI Registry for Vue & React',\n        tag: '58 / 60 chars',\n      },\n      {\n        id: 'meta-desc',\n        title: 'Meta Description Length',\n        description:\n          'Meta description is 152 characters (optimal range: 140–160 characters). Rich keyword density without truncation.',\n        status: 'pass',\n        value:\n          'Explore high-quality accessible Vue, React, and Angular UI components and blocks built with Tailwind CSS v4 and Lucide icons.',\n        tag: '152 / 160 chars',\n      },\n      {\n        id: 'canonical-url',\n        title: 'Canonical URL Tag',\n        description: 'Self-referencing canonical URL tag prevents duplicate content indexing across query variants.',\n        status: 'pass',\n        value: '<link rel=\"canonical\" href=\"https://uipkge.dev/vue/components\" />',\n        tag: 'Present',\n      },\n      {\n        id: 'meta-robots',\n        title: 'Meta Robots Directives',\n        description: 'Directives instruct crawlers to index the document and follow embedded hyperlinks.',\n        status: 'pass',\n        value: 'index, follow, max-image-preview:large, max-snippet:-1',\n        tag: 'Index, Follow',\n      },\n    ],\n  },\n  {\n    id: 'headings',\n    name: 'Heading Hierarchy & Structure',\n    icon: 'headings',\n    score: 100,\n    checks: [\n      {\n        id: 'h1-presence',\n        title: 'Single Primary H1 Tag',\n        description: 'Exactly 1 H1 tag detected containing target search terms and establishing clear topic authority.',\n        status: 'pass',\n        value: '<h1>UIPKGE Component & Block Registry</h1>',\n        tag: '1 H1 Tag',\n      },\n      {\n        id: 'h2-distribution',\n        title: 'H2 Section Organization',\n        description: '6 H2 tags logically divide major categories (Primitives, Blocks, Forms, Marketing, Feedback).',\n        status: 'pass',\n        value: '6 H2 tags · 0 skipped levels',\n        tag: '6 H2 Tags',\n      },\n      {\n        id: 'h3-nesting',\n        title: 'H3 Subsection Hierarchy',\n        description: '12 H3 tags cleanly nested under parent H2 sections without skipping hierarchical levels.',\n        status: 'pass',\n        value: '12 H3 tags · Valid tree hierarchy',\n        tag: '12 H3 Tags',\n      },\n      {\n        id: 'content-depth',\n        title: 'Content Word Count & Substance',\n        description: '1,840 words detected with strong semantic depth for component discovery.',\n        status: 'pass',\n        value: '1,840 words · 4.2 min read',\n        tag: 'Substantial',\n      },\n    ],\n  },\n  {\n    id: 'opengraph',\n    name: 'OpenGraph & Social Share Cards',\n    icon: 'opengraph',\n    score: 100,\n    checks: [\n      {\n        id: 'og-title-desc',\n        title: 'OpenGraph Title & Description',\n        description: 'Social graph tags are present and optimized for display across Twitter/X, LinkedIn, and Discord.',\n        status: 'pass',\n        value: 'og:title, og:description, og:type, og:url configured',\n        tag: 'Configured',\n      },\n      {\n        id: 'og-image',\n        title: 'OpenGraph Social Banner Image',\n        description:\n          '1200 × 630px high-resolution banner image detected with crisp aspect ratio (1.91:1) and fast CDN delivery.',\n        status: 'pass',\n        value: 'https://uipkge.dev/og/components.png (1200x630 · 84 KB)',\n        tag: '1200x630 px',\n      },\n      {\n        id: 'twitter-card',\n        title: 'Twitter / X Card Format',\n        description: 'Card format explicitly set to summary_large_image for maximal feed visibility.',\n        status: 'pass',\n        value: 'twitter:card = summary_large_image',\n        tag: 'Large Image',\n      },\n    ],\n  },\n  {\n    id: 'technical',\n    name: 'Performance, Images & Technical SEO',\n    icon: 'technical',\n    score: 85,\n    checks: [\n      {\n        id: 'image-alt-tags',\n        title: 'Image Alt Attributes',\n        description:\n          'All 24 embedded images contain meaningful, non-empty alt text attributes for accessibility and image search.',\n        status: 'pass',\n        value: '24 / 24 images with valid alt text (100%)',\n        tag: '24 / 24 Pass',\n      },\n      {\n        id: 'broken-links',\n        title: 'Crawlable Hyperlinks & 404 Errors',\n        description: 'Crawled 142 internal and external links. 0 broken links or redirect loops detected.',\n        status: 'pass',\n        value: '142 links verified · 0 errors',\n        tag: '0 Broken',\n      },\n      {\n        id: 'ssl-security',\n        title: 'SSL / HTTPS & Security Headers',\n        description: 'Valid TLS 1.3 certificate with HSTS header enabled and automatic HTTP-to-HTTPS redirect.',\n        status: 'pass',\n        value: 'TLS 1.3 · HSTS Active · HTTP/2 Supported',\n        tag: 'Secure',\n      },\n      {\n        id: 'schema-markup',\n        title: 'Schema.org Structured Data',\n        description:\n          'No SoftwareApplication or Product JSON-LD schema detected. Structured data enables rich Google SERP snippet features.',\n        status: 'warning',\n        value: 'Missing schema.org/SoftwareApplication microdata',\n        recommendation:\n          'Add JSON-LD script tag with SoftwareApplication schema to qualify for rich snippet search results.',\n        tag: 'Missing Schema',\n      },\n    ],\n  },\n]\n\nconst defaultOpenCategories = ['meta-tags', 'headings', 'opengraph', 'technical']\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-seo-audit-checklist, [ui-seo-audit-checklist]',\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: [\n    LucideAngularModule,\n    UiAccordionComponent,\n    UiAccordionContentComponent,\n    UiAccordionItemComponent,\n    UiAccordionTriggerComponent,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiCardDescriptionComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiInputComponent,\n    UiProgressComponent,\n    UiSeparatorComponent,\n  ],\n  template: `\n    <div [class]=\"rootClass\" data-slot=\"seo-audit-checklist\">\n      <!-- Header & Audit Control Center -->\n      <div ui-card class=\"border-border bg-card shadow-xs\">\n        <div ui-card-content class=\"p-5 sm:p-6\">\n          <div class=\"flex flex-col gap-5 lg:flex-row lg:items-center lg:justify-between\">\n            <!-- Left: Target URL & Score -->\n            <div class=\"space-y-3\">\n              <div class=\"flex flex-wrap items-center gap-3\">\n                <div\n                  class=\"border-success/30 bg-success/10 text-success flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs font-semibold\"\n                >\n                  <span class=\"relative flex size-2\">\n                    <span class=\"bg-success absolute inline-flex h-full w-full rounded-full opacity-75\"></span>\n                    <span class=\"bg-success relative inline-flex size-2 rounded-full\"></span>\n                  </span>\n                  <span class=\"font-mono tabular-nums\">{{ score }} / 100</span>\n                  <span>·</span>\n                  <span>Healthy Score</span>\n                </div>\n                <span ui-badge wrap variant=\"outline\" class=\"gap-1 text-xs font-normal\">\n                  <lucide-icon [img]=\"Globe\" class=\"text-muted-foreground size-3\" />\n                  HTTP 200 OK\n                </span>\n                <span ui-badge wrap variant=\"outline\" class=\"gap-1 text-xs font-normal\">\n                  <lucide-icon [img]=\"ShieldCheck\" class=\"text-success size-3\" />\n                  Indexable\n                </span>\n              </div>\n\n              <div>\n                <h1 class=\"text-foreground text-lg font-bold tracking-tight sm:text-xl\">\n                  On-Page SEO Analyzer & Snippet Simulator\n                </h1>\n                <p class=\"text-muted-foreground text-xs\">\n                  {{ lastAudited }}\n                </p>\n              </div>\n            </div>\n\n            <!-- Right: Action Buttons -->\n            <div class=\"flex flex-wrap items-center gap-2.5 sm:self-start lg:self-center\">\n              <button\n                ui-button\n                variant=\"outline\"\n                size=\"sm\"\n                class=\"h-9 gap-1.5 text-xs shadow-xs\"\n                [disabled]=\"isAuditing()\"\n                (click)=\"handleReaudit()\"\n              >\n                <lucide-icon [img]=\"RefreshCw\" [class]=\"reauditIconClass\" />\n                <span>Re-audit URL</span>\n              </button>\n\n              <button\n                ui-button\n                aria-label=\"Download attachment\"\n                size=\"sm\"\n                class=\"h-9 gap-1.5 text-xs font-medium shadow-xs\"\n                (click)=\"handleDownloadReport()\"\n              >\n                @if (!reportDownloaded()) {\n                  <lucide-icon [img]=\"FileDown\" class=\"size-3.5\" />\n                } @else {\n                  <lucide-icon [img]=\"Check\" class=\"text-success size-3.5\" />\n                }\n                <span>{{ reportDownloaded() ? 'Report Exported!' : 'Download SEO Report' }}</span>\n              </button>\n            </div>\n          </div>\n\n          <ui-separator class=\"my-4\" />\n\n          <!-- URL Input Strip -->\n          <div class=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n            <div class=\"relative flex-1\">\n              <div class=\"text-muted-foreground pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3\">\n                <lucide-icon [img]=\"Search\" class=\"size-4\" />\n              </div>\n              <ui-input\n                [value]=\"currentUrl()\"\n                (valueChange)=\"currentUrl.set($event)\"\n                type=\"url\"\n                placeholder=\"https://yourdomain.com/path\"\n                class=\"h-9 w-full rounded-md pr-24 pl-9 font-mono text-xs shadow-xs\"\n                (keydown.enter)=\"handleReaudit()\"\n              />\n              <button\n                type=\"button\"\n                class=\"text-muted-foreground hover:text-foreground absolute inset-y-0 right-0 flex min-h-6 items-center pr-3 text-xs transition-colors\"\n                (click)=\"handleCopyUrl()\"\n              >\n                @if (copiedUrl()) {\n                  <lucide-icon [img]=\"Check\" class=\"text-success size-3.5\" />\n                } @else {\n                  <lucide-icon [img]=\"Copy\" class=\"size-3.5\" />\n                }\n                <span class=\"ml-1 text-xs\">{{ copiedUrl() ? 'Copied' : 'Copy' }}</span>\n              </button>\n            </div>\n            <button\n              ui-button\n              size=\"sm\"\n              variant=\"secondary\"\n              class=\"h-9 shrink-0 px-4 text-xs font-medium\"\n              [disabled]=\"isAuditing()\"\n              (click)=\"handleReaudit()\"\n            >\n              <lucide-icon [img]=\"Search\" class=\"text-primary mr-1.5 size-3.5\" />\n              Analyze Page\n            </button>\n          </div>\n        </div>\n      </div>\n\n      <!-- 4 Core SEO Pillar Metric Cards -->\n      <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n        <!-- Pillar 1: Meta Tags & Snippet -->\n        <div ui-card class=\"border-border bg-card shadow-xs\">\n          <div ui-card-header class=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n            <div ui-card-title class=\"text-muted-foreground text-xs font-medium\">Meta Tags & Snippet</div>\n            <div\n              class=\"border-border/60 bg-muted/60 text-muted-foreground flex size-7 items-center justify-center rounded-md border\"\n            >\n              <lucide-icon [img]=\"FileText\" class=\"text-success size-3.5\" />\n            </div>\n          </div>\n          <div ui-card-content class=\"space-y-2\">\n            <div class=\"flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5\">\n              <div class=\"text-foreground text-xl font-bold tracking-tight sm:text-2xl\">100% Pass</div>\n              <span ui-badge wrap variant=\"success\" class=\"text-xs font-medium\">Optimal</span>\n            </div>\n            <div class=\"space-y-1.5\">\n              <ui-progress\n                [value]=\"100\"\n                class=\"bg-success/20 [&_[data-slot=progress-indicator]]:bg-success h-1.5 w-full\"\n              />\n              <p class=\"text-muted-foreground text-xs\">Title & description optimized</p>\n            </div>\n            <div\n              class=\"border-border/50 text-muted-foreground flex items-center justify-between border-t pt-2 font-mono text-xs\"\n            >\n              <span>58 chars title</span>\n              <span>·</span>\n              <span>152 chars desc</span>\n            </div>\n          </div>\n        </div>\n\n        <!-- Pillar 2: Core Web Vitals -->\n        <div ui-card class=\"border-border bg-card shadow-xs\">\n          <div ui-card-header class=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n            <div ui-card-title class=\"text-muted-foreground text-xs font-medium\">Core Web Vitals</div>\n            <div\n              class=\"border-border/60 bg-muted/60 text-muted-foreground flex size-7 items-center justify-center rounded-md border\"\n            >\n              <lucide-icon [img]=\"Zap\" class=\"text-success size-3.5\" />\n            </div>\n          </div>\n          <div ui-card-content class=\"space-y-2\">\n            <div class=\"flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5\">\n              <div class=\"text-foreground text-xl font-bold tracking-tight sm:text-2xl\">Good</div>\n              <span ui-badge wrap variant=\"success\" class=\"text-xs font-medium\">98/100</span>\n            </div>\n            <div class=\"space-y-1.5\">\n              <ui-progress\n                [value]=\"98\"\n                class=\"bg-success/20 [&_[data-slot=progress-indicator]]:bg-success h-1.5 w-full\"\n              />\n              <p class=\"text-muted-foreground text-xs\">LCP 1.2s · FID 12ms · CLS 0.01</p>\n            </div>\n            <div\n              class=\"border-border/50 text-muted-foreground flex items-center justify-between border-t pt-2 font-mono text-xs\"\n            >\n              <span>Chrome UX Report</span>\n              <span>·</span>\n              <span class=\"text-success\">Passed</span>\n            </div>\n          </div>\n        </div>\n\n        <!-- Pillar 3: Mobile Usability -->\n        <div ui-card class=\"border-border bg-card shadow-xs\">\n          <div ui-card-header class=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n            <div ui-card-title class=\"text-muted-foreground text-xs font-medium\">Mobile Usability</div>\n            <div\n              class=\"border-border/60 bg-muted/60 text-muted-foreground flex size-7 items-center justify-center rounded-md border\"\n            >\n              <lucide-icon [img]=\"Smartphone\" class=\"text-success size-3.5\" />\n            </div>\n          </div>\n          <div ui-card-content class=\"space-y-2\">\n            <div class=\"flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5\">\n              <div class=\"text-foreground text-xl font-bold tracking-tight sm:text-2xl\">100% Pass</div>\n              <span ui-badge wrap variant=\"success\" class=\"text-xs font-medium\">Valid</span>\n            </div>\n            <div class=\"space-y-1.5\">\n              <ui-progress\n                [value]=\"100\"\n                class=\"bg-success/20 [&_[data-slot=progress-indicator]]:bg-success h-1.5 w-full\"\n              />\n              <p class=\"text-muted-foreground text-xs\">Touch targets & viewport valid</p>\n            </div>\n            <div\n              class=\"border-border/50 text-muted-foreground flex items-center justify-between border-t pt-2 font-mono text-xs\"\n            >\n              <span>48px tap targets</span>\n              <span>·</span>\n              <span>0 horizontal scroll</span>\n            </div>\n          </div>\n        </div>\n\n        <!-- Pillar 4: Indexability & Crawl -->\n        <div ui-card class=\"border-border bg-card shadow-xs\">\n          <div ui-card-header class=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n            <div ui-card-title class=\"text-muted-foreground text-xs font-medium\">Indexability & Crawl</div>\n            <div\n              class=\"border-border/60 bg-muted/60 text-muted-foreground flex size-7 items-center justify-center rounded-md border\"\n            >\n              <lucide-icon [img]=\"ShieldCheck\" class=\"text-success size-3.5\" />\n            </div>\n          </div>\n          <div ui-card-content class=\"space-y-2\">\n            <div class=\"flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5\">\n              <div class=\"text-foreground text-xl font-bold tracking-tight sm:text-2xl\">Valid</div>\n              <span ui-badge wrap variant=\"success\" class=\"text-xs font-medium\">Indexed</span>\n            </div>\n            <div class=\"space-y-1.5\">\n              <ui-progress\n                [value]=\"95\"\n                class=\"bg-success/20 [&_[data-slot=progress-indicator]]:bg-success h-1.5 w-full\"\n              />\n              <p class=\"text-muted-foreground text-xs\">Canonical URL present & sitemap verified</p>\n            </div>\n            <div\n              class=\"border-border/50 text-muted-foreground flex items-center justify-between border-t pt-2 font-mono text-xs\"\n            >\n              <span>robots.txt allowed</span>\n              <span>·</span>\n              <span>sitemap.xml</span>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <!-- Google Search Result Snippet Preview Simulator -->\n      <div ui-card class=\"border-border bg-card shadow-xs\">\n        <div ui-card-header class=\"flex flex-col gap-3 pb-4 sm:flex-row sm:items-center sm:justify-between\">\n          <div>\n            <div class=\"flex items-center gap-2\">\n              <div ui-card-title class=\"text-base font-semibold\">Google Search Result Snippet Preview</div>\n              <span ui-badge wrap variant=\"outline\" class=\"gap-1 text-xs font-normal\">\n                <lucide-icon [img]=\"Eye\" class=\"text-primary size-3\" />\n                Live SERP Simulator\n              </span>\n            </div>\n            <div ui-card-description class=\"text-xs\">\n              Simulate how your meta tags, title, and URL render in Google SERPs on desktop vs. mobile.\n            </div>\n          </div>\n\n          <!-- Desktop / Mobile View Switcher -->\n          <div class=\"border-border bg-muted/50 flex items-center rounded-lg border p-0.5\">\n            <button type=\"button\" [class]=\"deviceButtonClass('desktop')\" (click)=\"previewDevice.set('desktop')\">\n              <lucide-icon [img]=\"Monitor\" class=\"size-3.5\" />\n              <span>Desktop</span>\n            </button>\n            <button type=\"button\" [class]=\"deviceButtonClass('mobile')\" (click)=\"previewDevice.set('mobile')\">\n              <lucide-icon [img]=\"Smartphone\" class=\"size-3.5\" />\n              <span>Mobile</span>\n            </button>\n          </div>\n        </div>\n\n        <div ui-card-content class=\"space-y-4\">\n          <!-- Mock Google SERP Result Box -->\n          <div [class]=\"serpBoxClass\">\n            <!-- Google Result Header / Breadcrumb -->\n            <div class=\"flex items-center gap-2.5 pb-1.5\">\n              <div\n                class=\"border-border bg-muted text-foreground flex size-7 shrink-0 items-center justify-center rounded-full border text-xs font-bold\"\n              >\n                ⚡\n              </div>\n              <div class=\"min-w-0 flex-1\">\n                <div class=\"text-foreground flex items-center gap-1.5 text-xs\">\n                  <span class=\"font-medium\">uipkge.dev</span>\n                  <span class=\"text-muted-foreground\">›</span>\n                  <span class=\"text-muted-foreground truncate\">vue › components</span>\n                </div>\n                <p class=\"text-muted-foreground truncate font-mono text-xs\">{{ urlValue() }}</p>\n              </div>\n            </div>\n\n            <!-- Google SERP Title -->\n            <div class=\"space-y-1 pt-1\">\n              <a\n                href=\"#\"\n                class=\"text-info block text-base leading-snug font-medium hover:underline sm:text-lg\"\n                (click)=\"$event.preventDefault()\"\n              >\n                {{ titleTag }}\n              </a>\n\n              <!-- Google Snippet Description with highlighted keywords -->\n              <p class=\"text-muted-foreground text-xs leading-relaxed sm:text-sm\">\n                <span class=\"text-muted-foreground/90 font-medium\">Aug 20, 2026 — </span>\n                Explore high-quality accessible\n                <strong class=\"text-foreground font-semibold\">Vue</strong> and\n                <strong class=\"text-foreground font-semibold\">React</strong>\n                <strong class=\"text-foreground font-semibold\">UI components</strong> and blocks built with\n                <strong class=\"text-foreground font-semibold\">Tailwind CSS v4</strong>, Reka UI, and Lucide icons.\n                Unbundled registry with full source code ownership.\n              </p>\n            </div>\n\n            <!-- Google Rich Snippet Badges -->\n            <div class=\"border-border/40 mt-3 flex flex-wrap items-center gap-2 border-t pt-2.5\">\n              <span ui-badge wrap variant=\"secondary\" class=\"text-xs font-normal\"> ★ 4.9 · 1.4k GitHub Stars </span>\n              <span ui-badge wrap variant=\"outline\" class=\"text-xs font-normal\"> Free & Open Source </span>\n              <span ui-badge wrap variant=\"outline\" class=\"text-xs font-normal\"> Vue 3.5 + React 19 </span>\n            </div>\n          </div>\n\n          <!-- Snippet Metrics Character / Pixel Budget Strip -->\n          <div class=\"border-border/80 bg-muted/30 grid grid-cols-1 gap-3 rounded-lg border p-3 sm:grid-cols-3\">\n            <div class=\"min-w-0 space-y-1\">\n              <div class=\"flex items-center justify-between text-xs\">\n                <span class=\"text-muted-foreground\">Title Tag Budget:</span>\n                <span class=\"text-foreground font-mono font-semibold tabular-nums\">58 / 60 chars</span>\n              </div>\n              <ui-progress\n                [value]=\"(58 / 60) * 100\"\n                class=\"bg-success/20 [&_[data-slot=progress-indicator]]:bg-success h-1.5 w-full\"\n              />\n              <p class=\"text-success text-xs\">Optimal (485px / 600px max SERP width)</p>\n            </div>\n\n            <div class=\"min-w-0 space-y-1\">\n              <div class=\"flex items-center justify-between text-xs\">\n                <span class=\"text-muted-foreground\">Meta Description:</span>\n                <span class=\"text-foreground font-mono font-semibold tabular-nums\">152 / 160 chars</span>\n              </div>\n              <ui-progress\n                [value]=\"(152 / 160) * 100\"\n                class=\"bg-success/20 [&_[data-slot=progress-indicator]]:bg-success h-1.5 w-full\"\n              />\n              <p class=\"text-success text-xs\">Optimal (890px / 960px max SERP width)</p>\n            </div>\n\n            <div class=\"flex flex-col justify-between space-y-1\">\n              <div class=\"flex items-center justify-between text-xs\">\n                <span class=\"text-muted-foreground\">Truncation Risk:</span>\n                <span ui-badge wrap variant=\"success\" class=\"px-1.5 py-0 text-xs\">0% Truncation</span>\n              </div>\n              <p class=\"text-muted-foreground text-xs\">Fully visible on both desktop & mobile Google viewports.</p>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <!-- Actionable Recommendation Callout Banner -->\n      <div ui-card class=\"border-warning/30 bg-warning/10 shadow-xs\">\n        <div ui-card-content class=\"p-5\">\n          <div class=\"flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between\">\n            <div class=\"flex items-start gap-3\">\n              <div\n                class=\"border-warning/30 bg-warning/20 text-warning mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg border\"\n              >\n                <lucide-icon [img]=\"AlertTriangle\" class=\"size-4\" />\n              </div>\n              <div class=\"space-y-1.5\">\n                <div class=\"flex flex-wrap items-center gap-2\">\n                  <h3 class=\"text-foreground text-sm font-semibold\">\n                    Actionable Recommendation: Add Schema.org JSON-LD Structured Data\n                  </h3>\n                  <span ui-badge wrap variant=\"warning\" class=\"text-xs\">High Impact</span>\n                </div>\n                <p class=\"text-muted-foreground text-xs leading-relaxed\">\n                  Adding\n                  <code\n                    class=\"bg-background/80 text-foreground border-border rounded border px-1 py-0.5 font-mono text-xs\"\n                    >SoftwareApplication</code\n                  >\n                  or\n                  <code\n                    class=\"bg-background/80 text-foreground border-border rounded border px-1 py-0.5 font-mono text-xs\"\n                    >TechArticle</code\n                  >\n                  structured data enables Google Rich Snippets, rating stars, and knowledge graph carousels.\n                </p>\n\n                <!-- Inline Code Preview -->\n                <div\n                  class=\"border-border/80 bg-background/90 text-foreground mt-2 rounded-md border p-3 font-mono text-xs\"\n                >\n                  <div class=\"text-muted-foreground flex items-center justify-between pb-1.5\">\n                    <span class=\"text-xs\">Recommended JSON-LD Block</span>\n                    <button\n                      type=\"button\"\n                      class=\"text-muted-foreground hover:text-foreground flex min-h-6 items-center gap-1 text-xs transition-colors\"\n                      (click)=\"handleCopySchema()\"\n                    >\n                      @if (copiedSchema()) {\n                        <lucide-icon [img]=\"Check\" class=\"text-success size-3\" />\n                      } @else {\n                        <lucide-icon [img]=\"Copy\" class=\"size-3\" />\n                      }\n                      <span>{{ copiedSchema() ? 'Copied' : 'Copy JSON-LD' }}</span>\n                    </button>\n                  </div>\n                  <pre class=\"text-muted-foreground overflow-x-auto text-xs leading-snug\"><code>{{\n                    jsonLdSnippet\n                  }}</code></pre>\n                </div>\n              </div>\n            </div>\n\n            <button\n              ui-button\n              variant=\"outline\"\n              size=\"sm\"\n              class=\"shrink-0 gap-1.5 text-xs shadow-xs\"\n              (click)=\"handleCopySchema()\"\n            >\n              <lucide-icon [img]=\"Code2\" class=\"size-3.5\" />\n              <span>Copy Schema Snippet</span>\n            </button>\n          </div>\n        </div>\n      </div>\n\n      <!-- Audit Checklist Grouped Accordion Section -->\n      <div class=\"space-y-4\">\n        <div class=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n          <div>\n            <h2 class=\"text-foreground text-sm font-semibold tracking-tight\">Detailed On-Page Audit Checklist</h2>\n            <p class=\"text-muted-foreground text-xs\">\n              Complete verification of on-page meta tags, heading semantics, social cards, and technical crawlability.\n            </p>\n          </div>\n\n          <!-- Filter Tabs -->\n          <div class=\"flex items-center gap-1.5\">\n            <button\n              ui-button\n              variant=\"ghost\"\n              size=\"sm\"\n              [class]=\"filterClass('all')\"\n              (click)=\"activeStatusFilter.set('all')\"\n            >\n              All Checks ({{ totalCount() }})\n            </button>\n            <button\n              ui-button\n              variant=\"ghost\"\n              size=\"sm\"\n              [class]=\"filterClass('pass')\"\n              (click)=\"activeStatusFilter.set('pass')\"\n            >\n              Passed ({{ passCount() }})\n            </button>\n            <button\n              ui-button\n              variant=\"ghost\"\n              size=\"sm\"\n              [class]=\"filterClass('warning')\"\n              (click)=\"activeStatusFilter.set('warning')\"\n            >\n              Warnings ({{ warningCount() }})\n            </button>\n          </div>\n        </div>\n\n        <!-- Accordion List -->\n        <ui-accordion type=\"multiple\" [defaultValue]=\"openCategories\" class=\"space-y-3\">\n          @for (category of filteredCategories(); track category.id) {\n            <ui-accordion-item\n              [value]=\"category.id\"\n              class=\"border-border bg-card overflow-hidden rounded-lg border shadow-xs\"\n            >\n              <button ui-accordion-trigger class=\"px-5 py-4 hover:no-underline\">\n                <div class=\"flex w-full flex-wrap items-center justify-between gap-3 pr-2 text-left\">\n                  <div class=\"flex items-center gap-3\">\n                    <div\n                      class=\"border-border/60 bg-muted/60 text-muted-foreground flex size-8 shrink-0 items-center justify-center rounded-md border\"\n                    >\n                      <lucide-icon [img]=\"categoryIcon(category.icon)\" class=\"size-4\" />\n                    </div>\n                    <div>\n                      <span class=\"text-foreground text-sm font-semibold\">{{ category.name }}</span>\n                      <p class=\"text-muted-foreground text-xs\">\n                        {{ passingCount(category) }} of {{ category.checks.length }} tests passing\n                      </p>\n                    </div>\n                  </div>\n\n                  <div class=\"flex items-center gap-2.5\">\n                    <span\n                      ui-badge\n                      [variant]=\"category.score === 100 ? 'success' : 'warning'\"\n                      class=\"text-xs font-semibold whitespace-normal tabular-nums\"\n                    >\n                      {{ category.score }}% Score\n                    </span>\n                  </div>\n                </div>\n              </button>\n\n              <ui-accordion-content class=\"px-5 pt-0 pb-5\">\n                <div class=\"divide-border/60 border-border/60 divide-y border-t\">\n                  @for (check of category.checks; track check.id) {\n                    <div class=\"space-y-2 py-3.5 first:pt-4 last:pb-1\">\n                      <div class=\"flex flex-col gap-1.5 sm:flex-row sm:items-start sm:justify-between sm:gap-3\">\n                        <div class=\"min-w-0 space-y-1\">\n                          <div class=\"flex items-center gap-2\">\n                            @if (check.status === 'pass') {\n                              <lucide-icon [img]=\"CheckCircle2\" class=\"text-success size-4 shrink-0\" />\n                            } @else if (check.status === 'warning') {\n                              <lucide-icon [img]=\"AlertTriangle\" class=\"text-warning size-4 shrink-0\" />\n                            } @else {\n                              <lucide-icon [img]=\"AlertCircle\" class=\"text-destructive size-4 shrink-0\" />\n                            }\n                            <h4 class=\"text-foreground text-xs font-semibold sm:text-sm\">{{ check.title }}</h4>\n                          </div>\n                          <p class=\"text-muted-foreground pl-6 text-xs leading-relaxed\">{{ check.description }}</p>\n                        </div>\n\n                        <div class=\"flex flex-wrap items-center gap-2 pl-6 sm:pl-0\">\n                          @if (check.tag) {\n                            <span ui-badge wrap variant=\"outline\" class=\"font-mono text-xs font-normal\">\n                              {{ check.tag }}\n                            </span>\n                          }\n                          <span\n                            ui-badge\n                            wrap\n                            [variant]=\"statusBadgeVariant(check.status)\"\n                            class=\"text-xs font-medium whitespace-normal capitalize\"\n                          >\n                            {{ check.status }}\n                          </span>\n                        </div>\n                      </div>\n\n                      <!-- Code / Value Inspector Box -->\n                      @if (check.value) {\n                        <div\n                          class=\"border-border/70 bg-muted/40 text-foreground ml-6 rounded-md border px-3 py-2 font-mono text-xs\"\n                        >\n                          <span class=\"text-xs break-all\">{{ check.value }}</span>\n                        </div>\n                      }\n\n                      <!-- Recommendation Callout -->\n                      @if (check.recommendation) {\n                        <div\n                          class=\"border-warning/30 bg-warning/10 text-warning ml-6 flex items-start gap-2 rounded-md border p-2.5 text-xs\"\n                        >\n                          <lucide-icon [img]=\"Info\" class=\"text-warning size-4 shrink-0\" />\n                          <p class=\"text-xs leading-relaxed\">{{ check.recommendation }}</p>\n                        </div>\n                      }\n                    </div>\n                  }\n                </div>\n              </ui-accordion-content>\n            </ui-accordion-item>\n          }\n        </ui-accordion>\n      </div>\n    </div>\n  `,\n})\nexport class UiSeoAuditChecklistComponent {\n  protected readonly AlertCircle = AlertCircle\n  protected readonly AlertTriangle = AlertTriangle\n  protected readonly Check = Check\n  protected readonly CheckCircle2 = CheckCircle2\n  protected readonly Code2 = Code2\n  protected readonly Copy = Copy\n  protected readonly Eye = Eye\n  protected readonly FileDown = FileDown\n  protected readonly FileText = FileText\n  protected readonly Globe = Globe\n  protected readonly Heading1 = Heading1\n  protected readonly Info = Info\n  protected readonly Lock = Lock\n  protected readonly Monitor = Monitor\n  protected readonly RefreshCw = RefreshCw\n  protected readonly Search = Search\n  protected readonly Share2 = Share2\n  protected readonly ShieldCheck = ShieldCheck\n  protected readonly Smartphone = Smartphone\n  protected readonly Zap = Zap\n\n  protected readonly openCategories = defaultOpenCategories\n  protected readonly jsonLdSnippet = `{\n  \"@context\": \"https://schema.org\",\n  \"@type\": \"SoftwareApplication\",\n  \"name\": \"UIPKGE\",\n  \"applicationCategory\": \"DeveloperApplication\",\n  \"operatingSystem\": \"Web\",\n  \"offers\": { \"@type\": \"Offer\", \"price\": \"0\", \"priceCurrency\": \"USD\" }\n}`\n\n  @Input() set url(v: string | undefined) {\n    const next = v ?? 'https://uipkge.dev/vue/components'\n    this.urlValue.set(next)\n    this.currentUrl.set(next)\n  }\n  @Input() score = 94\n  @Input() lastAudited = 'Audit completed in 640ms · Cached 2m ago'\n  @Input() titleTag = 'UIPKGE — Dual-Framework UI Registry for Vue & React'\n  @Input() metaDescription =\n    'Explore high-quality accessible Vue, React, and Angular UI components and blocks built with Tailwind CSS v4 and Lucide icons. Unbundled registry with full source code ownership.'\n  @Input() h1Tag = 'UIPKGE Component & Block Registry'\n  @Input() ogImageUrl = 'https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=1200&auto=format&fit=crop&q=80'\n  @Input() set categories(v: SeoCategory[] | undefined) {\n    this.categoriesSig.set(v ?? defaultCategories)\n  }\n  @Input() vitals: CoreVitalMetric[] = defaultVitals\n  @Input('class') className?: string\n\n  readonly urlValue = signal('https://uipkge.dev/vue/components')\n  readonly categoriesSig = signal<SeoCategory[]>(defaultCategories)\n\n  readonly currentUrl = signal('https://uipkge.dev/vue/components')\n  readonly isAuditing = signal(false)\n  readonly copiedSchema = signal(false)\n  readonly copiedUrl = signal(false)\n  readonly reportDownloaded = signal(false)\n  readonly previewDevice = signal<'desktop' | 'mobile'>('desktop')\n  readonly activeStatusFilter = signal<'all' | 'pass' | 'warning'>('all')\n\n  readonly allChecks = computed(() => this.categoriesSig().flatMap((cat) => cat.checks))\n  readonly passCount = computed(() => this.allChecks().filter((c) => c.status === 'pass').length)\n  readonly warningCount = computed(() => this.allChecks().filter((c) => c.status === 'warning').length)\n  readonly totalCount = computed(() => this.allChecks().length)\n\n  readonly filteredCategories = computed(() => {\n    const filter = this.activeStatusFilter()\n    const cats = this.categoriesSig()\n    if (filter === 'all') return cats\n    return cats\n      .map((cat) => ({ ...cat, checks: cat.checks.filter((check) => check.status === filter) }))\n      .filter((cat) => cat.checks.length > 0)\n  })\n\n  get rootClass(): string {\n    return cn('w-full space-y-6', this.className)\n  }\n\n  get reauditIconClass(): string {\n    return cn('size-3.5', this.isAuditing() && 'animate-spin')\n  }\n\n  get serpBoxClass(): string {\n    return cn(\n      'border-border/80 bg-background mx-auto rounded-xl border p-4 shadow-xs transition-colors duration-200 sm:p-5',\n      this.previewDevice() === 'mobile' ? 'border-border max-w-md' : 'w-full',\n    )\n  }\n\n  deviceButtonClass(device: 'desktop' | 'mobile'): string {\n    return cn(\n      'flex min-h-6 items-center gap-1.5 rounded-md px-3 py-1 text-xs font-medium transition-colors',\n      this.previewDevice() === device\n        ? 'bg-background text-foreground shadow-xs'\n        : 'text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  filterClass(filter: 'all' | 'pass' | 'warning'): string {\n    const active = this.activeStatusFilter() === filter\n    const activeColor =\n      filter === 'pass'\n        ? 'bg-muted text-success font-medium'\n        : filter === 'warning'\n          ? 'bg-muted text-warning font-medium'\n          : 'bg-muted text-foreground font-medium'\n    return cn('h-8 px-2.5 text-xs', active ? activeColor : 'text-muted-foreground')\n  }\n\n  categoryIcon(icon: SeoCategory['icon']): typeof FileText {\n    if (icon === 'headings') return Heading1\n    if (icon === 'opengraph') return Share2\n    if (icon === 'technical') return Lock\n    return FileText\n  }\n\n  statusBadgeVariant(status: CheckStatus): 'success' | 'warning' | 'destructive' | 'info' {\n    if (status === 'warning') return 'warning'\n    if (status === 'fail') return 'destructive'\n    if (status === 'info') return 'info'\n    return 'success'\n  }\n\n  passingCount(category: SeoCategory): number {\n    return category.checks.filter((c) => c.status === 'pass').length\n  }\n\n  handleReaudit(): void {\n    this.isAuditing.set(true)\n    setTimeout(() => this.isAuditing.set(false), 500)\n  }\n\n  handleCopySchema(): void {\n    try {\n      void navigator.clipboard.writeText(this.jsonLdSnippet)\n    } catch {\n      // Clipboard unavailable: still flip the label.\n    }\n    this.copiedSchema.set(true)\n    setTimeout(() => this.copiedSchema.set(false), 1800)\n  }\n\n  handleCopyUrl(): void {\n    try {\n      void navigator.clipboard.writeText(this.currentUrl())\n    } catch {\n      // Clipboard unavailable: still flip the label.\n    }\n    this.copiedUrl.set(true)\n    setTimeout(() => this.copiedUrl.set(false), 1800)\n  }\n\n  handleDownloadReport(): void {\n    this.reportDownloaded.set(true)\n    setTimeout(() => this.reportDownloaded.set(false), 2000)\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/seo-audit-checklist/seo-audit-checklist.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/seo-audit-checklist/index.ts",
      "content": "export {\n  UiSeoAuditChecklistComponent,\n  type CheckStatus,\n  type SeoCheckItem,\n  type SeoCategory,\n  type CoreVitalMetric,\n} from './seo-audit-checklist.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/seo-audit-checklist/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/accordion.json",
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/card.json",
    "https://uipkge.dev/r/angular/input.json",
    "https://uipkge.dev/r/angular/progress.json",
    "https://uipkge.dev/r/angular/separator.json"
  ],
  "description": "Ahrefs and SEMrush style on-page SEO analyzer, meta tag auditor, Google SERP snippet preview simulator, heading hierarchy validator, and Core Web Vitals checklist.",
  "categories": [
    "marketing",
    "dashboard"
  ]
}