{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "careers-job-board",
  "title": "Careers Job Board",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/careers-job-board/careers-job-board.component.ts",
      "content": "// Careers page and job openings board with perks overview, live department &\n// location filtering, keyword search, grouped department openings, and a general\n// application CTA. Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'\nimport {\n  ArrowRight,\n  Briefcase,\n  Coins,\n  Globe,\n  GraduationCap,\n  HeartHandshake,\n  LucideAngularModule,\n  MapPin,\n  Search,\n  SearchX,\n  TrendingUp,\n  type LucideIconData,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport { UiInputComponent } from '@/ui/input/input.component'\nimport {\n  UiSelectComponent,\n  UiSelectContentComponent,\n  UiSelectItemComponent,\n  UiSelectTriggerComponent,\n  UiSelectValueComponent,\n} from '@/ui/select/select.component'\n\ninterface Perk {\n  icon: LucideIconData\n  title: string\n  description: string\n}\n\nexport interface JobOpening {\n  id: string\n  title: string\n  department: 'Engineering' | 'Design' | 'Product' | 'Marketing'\n  location: 'Remote' | 'San Francisco, CA' | 'London, UK'\n  type: string\n  level: string\n  salary: string\n  description: string\n}\n\nconst PERKS: Perk[] = [\n  {\n    icon: Globe,\n    title: 'Remote-first',\n    description: 'Work from anywhere in the world with flexible hours.',\n  },\n  {\n    icon: GraduationCap,\n    title: '$5k learning budget',\n    description: 'Annual stipend for courses, books, tools, and conferences.',\n  },\n  {\n    icon: HeartHandshake,\n    title: '100% health coverage',\n    description: 'Comprehensive medical, dental, and vision for you and dependents.',\n  },\n  {\n    icon: TrendingUp,\n    title: 'Equity packages',\n    description: 'Meaningful ownership stake with transparent vesting schedules.',\n  },\n]\n\nconst DEPARTMENTS = [\n  { id: 'all', label: 'All', count: 12 },\n  { id: 'Engineering', label: 'Engineering', count: 6 },\n  { id: 'Design', label: 'Design', count: 2 },\n  { id: 'Product', label: 'Product', count: 2 },\n  { id: 'Marketing', label: 'Marketing', count: 2 },\n] as const\n\nconst LOCATIONS = [\n  { id: 'all', label: 'All Locations' },\n  { id: 'Remote', label: 'Remote' },\n  { id: 'San Francisco, CA', label: 'San Francisco, CA' },\n  { id: 'London, UK', label: 'London, UK' },\n] as const\n\nconst JOBS: JobOpening[] = [\n  {\n    id: 'eng-1',\n    title: 'Staff Frontend Engineer',\n    department: 'Engineering',\n    location: 'Remote',\n    type: 'Full-time',\n    level: 'Staff',\n    salary: '$190k - $240k · 0.3% - 0.6%',\n    description:\n      'Lead the architecture of our high-performance UI components, compiler toolchains, and multi-framework design systems.',\n  },\n  {\n    id: 'eng-2',\n    title: 'Senior Full-Stack Engineer',\n    department: 'Engineering',\n    location: 'San Francisco, CA',\n    type: 'Full-time',\n    level: 'Senior',\n    salary: '$160k - $210k · 0.25% - 0.5%',\n    description:\n      'Build end-to-end features spanning serverless edge runtimes, component registries, and developer tooling.',\n  },\n  {\n    id: 'eng-3',\n    title: 'Systems & Performance Engineer',\n    department: 'Engineering',\n    location: 'Remote',\n    type: 'Full-time',\n    level: 'Senior',\n    salary: '$170k - $220k · 0.25% - 0.5%',\n    description:\n      'Optimize bundling pipelines, AST transformers, and static site generation latency across large workspaces.',\n  },\n  {\n    id: 'eng-4',\n    title: 'Design Systems Engineer',\n    department: 'Engineering',\n    location: 'London, UK',\n    type: 'Full-time',\n    level: 'Mid-Senior',\n    salary: '£110k - £140k · 0.2% - 0.4%',\n    description:\n      'Bridge design and code by maintaining tokens, accessible headless primitives, and fluid interaction standards.',\n  },\n  {\n    id: 'eng-5',\n    title: 'Developer Experience Engineer',\n    department: 'Engineering',\n    location: 'Remote',\n    type: 'Full-time',\n    level: 'Mid-Senior',\n    salary: '$150k - $190k · 0.2% - 0.4%',\n    description:\n      'Craft exceptional CLI workflows, interactive documentation playgrounds, and starter template engines.',\n  },\n  {\n    id: 'eng-6',\n    title: 'QA & Automation Engineer',\n    department: 'Engineering',\n    location: 'London, UK',\n    type: 'Full-time',\n    level: 'Mid-Senior',\n    salary: '£95k - £125k · 0.15% - 0.3%',\n    description:\n      'Expand cross-browser visual regression testing suites, accessibility audits, and automated release checks.',\n  },\n  {\n    id: 'des-1',\n    title: 'Senior Product Designer',\n    department: 'Design',\n    location: 'San Francisco, CA',\n    type: 'Full-time',\n    level: 'Senior',\n    salary: '$155k - $195k · 0.25% - 0.45%',\n    description:\n      'Define micro-interactions, responsive dashboard workflows, and layout ergonomics across our block catalog.',\n  },\n  {\n    id: 'des-2',\n    title: 'Brand & Motion Designer',\n    department: 'Design',\n    location: 'Remote',\n    type: 'Full-time',\n    level: 'Mid-Senior',\n    salary: '$140k - $180k · 0.2% - 0.35%',\n    description:\n      'Create fluid motion principles, brand identity assets, and polished launch visuals for major releases.',\n  },\n  {\n    id: 'prd-1',\n    title: 'Staff Technical Product Manager',\n    department: 'Product',\n    location: 'Remote',\n    type: 'Full-time',\n    level: 'Staff',\n    salary: '$180k - $230k · 0.3% - 0.5%',\n    description:\n      'Drive product roadmap for developer workflows, unbundled registry distribution, and monorepo orchestration.',\n  },\n  {\n    id: 'prd-2',\n    title: 'Product Operations Lead',\n    department: 'Product',\n    location: 'San Francisco, CA',\n    type: 'Full-time',\n    level: 'Senior',\n    salary: '$145k - $185k · 0.2% - 0.35%',\n    description: 'Manage release triage, community feature requests, and cross-team execution cadences.',\n  },\n  {\n    id: 'mkt-1',\n    title: 'Developer Advocate / Technical Writer',\n    department: 'Marketing',\n    location: 'Remote',\n    type: 'Full-time',\n    level: 'Mid-Senior',\n    salary: '$140k - $180k · 0.2% - 0.4%',\n    description:\n      'Author technical deep-dives, create interactive video tutorials, and engage with the open-source community.',\n  },\n  {\n    id: 'mkt-2',\n    title: 'Growth Marketing Lead',\n    department: 'Marketing',\n    location: 'London, UK',\n    type: 'Full-time',\n    level: 'Senior',\n    salary: '£105k - £135k · 0.2% - 0.4%',\n    description:\n      'Lead developer-focused growth experiments, SEO architecture, launch campaigns, and analytics instrumentation.',\n  },\n]\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-careers-job-board, [ui-careers-job-board]',\n  standalone: true,\n  // React renders the root <section> itself: the host stays out of layout and `class` goes to the root.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [\n    LucideAngularModule,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiInputComponent,\n    UiSelectComponent,\n    UiSelectContentComponent,\n    UiSelectItemComponent,\n    UiSelectTriggerComponent,\n    UiSelectValueComponent,\n  ],\n  template: `\n    <section data-slot=\"careers-job-board\" [class]=\"rootClass\">\n      <!-- Hero / Header -->\n      <div class=\"mx-auto max-w-3xl space-y-4 text-center\">\n        <span ui-badge variant=\"secondary\" class=\"gap-1.5 px-3 py-1 text-xs font-medium\">\n          <lucide-icon [img]=\"Briefcase\" class=\"text-primary size-3.5\" />\n          We&apos;re hiring\n        </span>\n        <h1 class=\"text-foreground text-3xl font-bold tracking-tight sm:text-4xl lg:text-5xl\">{{ titleInput() }}</h1>\n        <p class=\"text-muted-foreground text-base leading-relaxed sm:text-lg\">{{ subtitleInput() }}</p>\n      </div>\n\n      <!-- Perks summary row -->\n      <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n        @for (perk of perks; track perk.title) {\n          <div class=\"bg-card border-border flex flex-col gap-3 rounded-xl border p-4 shadow-xs\">\n            <div class=\"bg-primary/10 text-primary flex size-9 items-center justify-center rounded-lg\">\n              <lucide-icon [img]=\"perk.icon\" class=\"size-4.5\" />\n            </div>\n            <div class=\"space-y-1\">\n              <p class=\"text-foreground text-sm font-semibold\">{{ perk.title }}</p>\n              <p class=\"text-muted-foreground text-xs leading-relaxed\">{{ perk.description }}</p>\n            </div>\n          </div>\n        }\n      </div>\n\n      <!-- Filter Toolbar -->\n      <div class=\"bg-card border-border space-y-4 rounded-xl border p-4 shadow-xs\">\n        <div class=\"flex flex-wrap items-center gap-1.5\">\n          @for (dept of departments; track dept.id) {\n            <button\n              ui-button\n              [variant]=\"selectedDepartment() === dept.id ? 'default' : 'outline'\"\n              size=\"sm\"\n              class=\"h-8 rounded-lg text-xs\"\n              (click)=\"selectedDepartment.set(dept.id)\"\n            >\n              {{ dept.label }}\n              <span [class]=\"deptCountClass(dept.id)\">\n                {{ dept.count }}\n              </span>\n            </button>\n          }\n        </div>\n\n        <div class=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n          <div class=\"relative flex-1\">\n            <lucide-icon\n              [img]=\"Search\"\n              class=\"text-muted-foreground pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2\"\n            />\n            <ui-input\n              [value]=\"searchQuery()\"\n              (valueChange)=\"searchQuery.set($event)\"\n              placeholder=\"Search roles, technologies, or keywords...\"\n              class=\"pl-9 text-sm\"\n              aria-label=\"Search open positions\"\n            />\n          </div>\n\n          <div class=\"w-full shrink-0 sm:w-56\">\n            <ui-select [value]=\"selectedLocation()\" (valueChange)=\"selectedLocation.set($event)\">\n              <button ui-select-trigger class=\"w-full text-xs sm:text-sm\" aria-label=\"Filter by location\">\n                <ui-select-value placeholder=\"All Locations\" />\n              </button>\n              <ui-select-content>\n                @for (loc of locations; track loc.id) {\n                  <ui-select-item [value]=\"loc.id\">{{ loc.label }}</ui-select-item>\n                }\n              </ui-select-content>\n            </ui-select>\n          </div>\n\n          @if (isFiltered()) {\n            <button\n              ui-button\n              variant=\"ghost\"\n              size=\"sm\"\n              class=\"text-muted-foreground hover:text-foreground text-xs\"\n              (click)=\"resetFilters()\"\n            >\n              Reset filters\n            </button>\n          }\n        </div>\n      </div>\n\n      <!-- Grouped Job Openings list -->\n      @if (departmentGroups().length > 0) {\n        <div class=\"space-y-10\">\n          @for (group of departmentGroups(); track group.name) {\n            <div class=\"space-y-4\">\n              <div class=\"border-border flex items-center justify-between border-b pb-3\">\n                <div class=\"flex items-center gap-2.5\">\n                  <h2 class=\"text-foreground text-lg font-semibold tracking-tight\">{{ group.name }}</h2>\n                  <span ui-badge variant=\"secondary\" class=\"text-xs font-normal\">\n                    {{ group.jobs.length }} {{ group.jobs.length === 1 ? 'opening' : 'openings' }}\n                  </span>\n                </div>\n              </div>\n\n              <div class=\"space-y-3\">\n                @for (job of group.jobs; track job.id) {\n                  <article\n                    class=\"group bg-card border-border hover:border-primary/40 relative flex flex-col justify-between gap-4 rounded-xl border p-5 shadow-xs transition-colors duration-150 hover:shadow-sm md:flex-row md:items-center\"\n                  >\n                    <div class=\"min-w-0 flex-1 space-y-2\">\n                      <div class=\"flex flex-wrap items-center gap-2\">\n                        <h3\n                          class=\"group-hover:text-primary text-foreground text-base font-semibold tracking-tight transition-colors sm:text-lg\"\n                        >\n                          {{ job.title }}\n                        </h3>\n                        <span ui-badge variant=\"outline\" class=\"text-xs\">{{ job.level }}</span>\n                      </div>\n                      <p class=\"text-muted-foreground line-clamp-2 text-sm leading-relaxed\">{{ job.description }}</p>\n                      <div class=\"text-muted-foreground flex flex-wrap items-center gap-x-4 gap-y-2 pt-1 text-xs\">\n                        <span class=\"inline-flex items-center gap-1.5\">\n                          <lucide-icon [img]=\"MapPin\" class=\"text-muted-foreground size-3.5\" />\n                          {{ job.location }}\n                        </span>\n                        <span class=\"inline-flex items-center gap-1.5\">\n                          <lucide-icon [img]=\"Briefcase\" class=\"text-muted-foreground size-3.5\" />\n                          {{ job.type }}\n                        </span>\n                        <span class=\"text-foreground inline-flex items-center gap-1.5 font-medium\">\n                          <lucide-icon [img]=\"Coins\" class=\"text-muted-foreground size-3.5\" />\n                          {{ job.salary }}\n                        </span>\n                      </div>\n                    </div>\n                    <div class=\"shrink-0 pt-2 md:pt-0\">\n                      <button ui-button class=\"w-full gap-1.5 md:w-auto\" size=\"sm\">\n                        Apply now\n                        <lucide-icon\n                          [img]=\"ArrowRight\"\n                          class=\"size-3.5 transition-transform group-hover:translate-x-0.5\"\n                        />\n                      </button>\n                    </div>\n                  </article>\n                }\n              </div>\n            </div>\n          }\n        </div>\n      } @else {\n        <!-- Empty State -->\n        <div\n          class=\"border-border flex flex-col items-center justify-center rounded-xl border border-dashed p-12 text-center\"\n        >\n          <div class=\"bg-muted text-muted-foreground mb-4 flex size-12 items-center justify-center rounded-full\">\n            <lucide-icon [img]=\"SearchX\" class=\"size-6\" />\n          </div>\n          <h3 class=\"text-foreground text-base font-semibold\">No matching positions found</h3>\n          <p class=\"text-muted-foreground mt-1 mb-4 max-w-sm text-sm leading-normal\">\n            We could not find any roles matching your current search or filters. Try adjusting your criteria.\n          </p>\n          <button ui-button variant=\"outline\" size=\"sm\" (click)=\"resetFilters()\">Clear all filters</button>\n        </div>\n      }\n\n      <!-- Don't see your role? CTA Card -->\n      <div\n        class=\"bg-card border-border flex flex-col items-start justify-between gap-6 rounded-2xl border p-6 shadow-xs sm:flex-row sm:items-center sm:p-8\"\n      >\n        <div class=\"max-w-xl space-y-1.5\">\n          <h3 class=\"text-foreground text-lg font-semibold tracking-tight\">Don&apos;t see your role?</h3>\n          <p class=\"text-muted-foreground text-sm leading-relaxed\">\n            We are always looking for curious, high-craft engineers, designers, and thinkers. Send us an open\n            application and tell us what you&apos;d love to build with us.\n          </p>\n        </div>\n        <button ui-button variant=\"outline\" class=\"shrink-0 gap-2\">\n          Send open application\n          <lucide-icon [img]=\"ArrowRight\" class=\"size-4\" />\n        </button>\n      </div>\n    </section>\n  `,\n})\nexport class UiCareersJobBoardComponent {\n  protected readonly ArrowRight = ArrowRight\n  protected readonly Briefcase = Briefcase\n  protected readonly Coins = Coins\n  protected readonly MapPin = MapPin\n  protected readonly Search = Search\n  protected readonly SearchX = SearchX\n\n  protected readonly perks = PERKS\n  protected readonly departments = DEPARTMENTS\n  protected readonly locations = LOCATIONS\n\n  readonly titleInput = signal('Join our team')\n  readonly subtitleInput = signal('Help us build the open-source UI infrastructure of tomorrow')\n\n  // React props are `title` / `subtitle`; `title` shadows the DOM property so the\n  // signal keeps an internal name and the alias exposes the canonical binding.\n  @Input({ alias: 'title' }) set titleProp(v: string | undefined) {\n    if (v !== undefined) this.titleInput.set(v)\n  }\n  @Input({ alias: 'subtitle' }) set subtitleProp(v: string | undefined) {\n    if (v !== undefined) this.subtitleInput.set(v)\n  }\n  @Input('class') className?: string\n\n  readonly selectedDepartment = signal<string>('all')\n  readonly selectedLocation = signal<string>('all')\n  readonly searchQuery = signal('')\n\n  readonly filteredJobs = computed(() => {\n    const q = this.searchQuery().trim().toLowerCase()\n    return JOBS.filter((job) => {\n      const matchDept = this.selectedDepartment() === 'all' || job.department === this.selectedDepartment()\n      const matchLoc = this.selectedLocation() === 'all' || job.location === this.selectedLocation()\n      if (!matchDept || !matchLoc) return false\n      if (!q) return true\n      return (\n        job.title.toLowerCase().includes(q) ||\n        job.description.toLowerCase().includes(q) ||\n        job.department.toLowerCase().includes(q) ||\n        job.level.toLowerCase().includes(q) ||\n        job.location.toLowerCase().includes(q) ||\n        job.type.toLowerCase().includes(q) ||\n        job.salary.toLowerCase().includes(q)\n      )\n    })\n  })\n\n  readonly departmentGroups = computed(() => {\n    const allDepts = ['Engineering', 'Design', 'Product', 'Marketing'] as const\n    const sel = this.selectedDepartment()\n    const target = sel === 'all' ? allDepts : allDepts.filter((dept) => dept === sel)\n    return target\n      .map((dept) => ({ name: dept, jobs: this.filteredJobs().filter((job) => job.department === dept) }))\n      .filter((group) => group.jobs.length > 0)\n  })\n\n  readonly isFiltered = computed(\n    () => this.selectedDepartment() !== 'all' || this.selectedLocation() !== 'all' || this.searchQuery().trim() !== '',\n  )\n\n  get rootClass(): string {\n    return cn('w-full space-y-12 py-6', this.className)\n  }\n\n  deptCountClass(id: string): string {\n    return cn(\n      'ml-1.5 rounded-full px-1.5 py-0.5 text-xs font-medium tabular-nums',\n      this.selectedDepartment() === id\n        ? 'bg-primary-foreground/20 text-primary-foreground'\n        : 'bg-muted text-muted-foreground',\n    )\n  }\n\n  resetFilters(): void {\n    this.selectedDepartment.set('all')\n    this.selectedLocation.set('all')\n    this.searchQuery.set('')\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/careers-job-board/careers-job-board.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/careers-job-board/index.ts",
      "content": "export { UiCareersJobBoardComponent, type JobOpening } from './careers-job-board.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/careers-job-board/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/input.json",
    "https://uipkge.dev/r/angular/select.json"
  ],
  "description": "Careers page and job openings board with perks overview, live department & location filtering, keyword search, grouped department openings, and general application CTA.",
  "categories": [
    "marketing"
  ]
}