Menu

Change language

GitHub repo

Compensation Band Benchmarking

blockhr

Pave/Levels.fyi style engineering salary and equity compensation band visualizer with P25, P50, P75, P90 percentile ranges, level progression matrix, compa-ratio diagnostics, and certified pay equity audit.

Installation

$npx uipkge-ng@latest add compensation-band-benchmarking
Manifest:https://uipkge.dev/r/angular/compensation-band-benchmarking.jsonInstalls to:src/app/components/blocks/compensation-band-benchmarking/

Variants

Loading interactive previews…

Schema

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

CompensationLevel
interface CompensationLevel {
  id: string
  code: string
  title: string
  roleTitle: string
  experience: string
  scope: string
  headcount: number
  p25Base: number
  p50Base: number
  p75Base: number
  p90Base: number
  equityMin: number
  equityMax: number
  equityP50: number
  totalMin: number
  totalMax: number
  totalP50: number
  compaRatio: number
  marketP50Base: number
  marketP50Equity: number
  marketP50Total: number
  equityPercent: number
  badgeVariant: 'default' | 'secondary' | 'outline' | 'info'
}

Files installed (2)

  • src/app/components/blocks/compensation-band-benchmarking/compensation-band-benchmarking.component.ts37.7 kB
    // Pave/Levels.fyi style engineering salary and equity compensation band visualizer
    // with P25, P50, P75, P90 percentile ranges, level progression matrix, compa-ratio
    // diagnostics, and certified pay equity audit. Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'
    import {
      Award,
      BadgeCheck,
      Building2,
      CheckCircle2,
      Coins,
      DollarSign,
      Download,
      Layers,
      LucideAngularModule,
      Scale,
      ShieldCheck,
      TrendingUp,
      Users,
    } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiBadgeComponent } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import {
      UiCardComponent,
      UiCardContentComponent,
      UiCardDescriptionComponent,
      UiCardHeaderComponent,
      UiCardTitleComponent,
    } from '@/ui/card/card.component'
    import { UiProgressComponent } from '@/ui/progress/progress.component'
    import {
      UiSelectComponent,
      UiSelectContentComponent,
      UiSelectItemComponent,
      UiSelectTriggerComponent,
      UiSelectValueComponent,
    } from '@/ui/select/select.component'
    import { UiSeparatorComponent } from '@/ui/separator/separator.component'
    import {
      UiTableBodyComponent,
      UiTableCellComponent,
      UiTableComponent,
      UiTableHeadComponent,
      UiTableHeaderComponent,
      UiTableRowComponent,
    } from '@/ui/table/table.component'
    
    export interface CompensationLevel {
      id: string
      code: string
      title: string
      roleTitle: string
      experience: string
      scope: string
      headcount: number
      p25Base: number
      p50Base: number
      p75Base: number
      p90Base: number
      equityMin: number
      equityMax: number
      equityP50: number
      totalMin: number
      totalMax: number
      totalP50: number
      compaRatio: number
      marketP50Base: number
      marketP50Equity: number
      marketP50Total: number
      equityPercent: number
      badgeVariant: 'default' | 'secondary' | 'outline' | 'info'
    }
    
    const defaultLevels: CompensationLevel[] = [
      {
        id: 'ic3',
        code: 'IC3',
        title: 'Software Engineer',
        roleTitle: 'Software Engineer (IC3)',
        experience: '1–3 yrs experience',
        scope: 'Feature delivery, component unit tests, and production code execution.',
        headcount: 28,
        p25Base: 135000,
        p50Base: 150000,
        p75Base: 168000,
        p90Base: 185000,
        equityMin: 35000,
        equityMax: 60000,
        equityP50: 45000,
        totalMin: 170000,
        totalMax: 245000,
        totalP50: 195000,
        compaRatio: 101.2,
        marketP50Base: 148000,
        marketP50Equity: 44000,
        marketP50Total: 192000,
        equityPercent: 23.1,
        badgeVariant: 'outline',
      },
      {
        id: 'ic4',
        code: 'IC4',
        title: 'Senior Engineer',
        roleTitle: 'Senior Software Engineer (IC4)',
        experience: '3–6 yrs experience',
        scope: 'Autonomous service ownership, technical RFC design, and mentoring.',
        headcount: 42,
        p25Base: 170000,
        p50Base: 190000,
        p75Base: 210000,
        p90Base: 230000,
        equityMin: 70000,
        equityMax: 110000,
        equityP50: 90000,
        totalMin: 240000,
        totalMax: 340000,
        totalP50: 280000,
        compaRatio: 101.8,
        marketP50Base: 187000,
        marketP50Equity: 88000,
        marketP50Total: 275000,
        equityPercent: 32.1,
        badgeVariant: 'secondary',
      },
      {
        id: 'ic5',
        code: 'IC5',
        title: 'Staff Engineer',
        roleTitle: 'Staff Software Engineer (IC5)',
        experience: '6–10 yrs experience',
        scope: 'Cross-team architecture, platform reliability, and engineering standards.',
        headcount: 18,
        p25Base: 205000,
        p50Base: 225000,
        p75Base: 250000,
        p90Base: 275000,
        equityMin: 120000,
        equityMax: 180000,
        equityP50: 140000,
        totalMin: 325000,
        totalMax: 455000,
        totalP50: 365000,
        compaRatio: 102.4,
        marketP50Base: 220000,
        marketP50Equity: 136000,
        marketP50Total: 356000,
        equityPercent: 38.4,
        badgeVariant: 'default',
      },
      {
        id: 'ic6',
        code: 'IC6',
        title: 'Senior Staff Engineer',
        roleTitle: 'Senior Staff Software Engineer (IC6)',
        experience: '10–14 yrs experience',
        scope: 'Org-wide strategic tech vision, high-scale resilience, and executive partnership.',
        headcount: 9,
        p25Base: 240000,
        p50Base: 265000,
        p75Base: 295000,
        p90Base: 330000,
        equityMin: 180000,
        equityMax: 280000,
        equityP50: 225000,
        totalMin: 420000,
        totalMax: 610000,
        totalP50: 490000,
        compaRatio: 103.1,
        marketP50Base: 258000,
        marketP50Equity: 218000,
        marketP50Total: 476000,
        equityPercent: 45.9,
        badgeVariant: 'info',
      },
      {
        id: 'ic7',
        code: 'IC7',
        title: 'Principal Engineer',
        roleTitle: 'Principal Software Engineer (IC7)',
        experience: '14+ yrs experience',
        scope: 'Company-level technology roadmap, deep R&D, and industry IP leadership.',
        headcount: 4,
        p25Base: 280000,
        p50Base: 315000,
        p75Base: 355000,
        p90Base: 400000,
        equityMin: 280000,
        equityMax: 450000,
        equityP50: 360000,
        totalMin: 560000,
        totalMax: 850000,
        totalP50: 675000,
        compaRatio: 104.0,
        marketP50Base: 305000,
        marketP50Equity: 345000,
        marketP50Total: 650000,
        equityPercent: 53.3,
        badgeVariant: 'default',
      },
    ]
    
    // Scale configuration for multi-bar percentiles ($100k to $420k)
    const SCALE_MIN = 100000
    const SCALE_MAX = 420000
    const SCALE_RANGE = SCALE_MAX - SCALE_MIN
    
    function getPercent(val: number): number {
      return Math.max(0, Math.min(100, ((val - SCALE_MIN) / SCALE_RANGE) * 100))
    }
    
    function fmtCurrency(val: number): string {
      return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(val)
    }
    
    function fmtK(val: number): string {
      return `$${Math.round(val / 1000)}k`
    }
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-compensation-band-benchmarking, [ui-compensation-band-benchmarking]',
      standalone: true,
      // React renders the root <div> itself: the host stays out of layout and `class` goes to the root.
      host: { '[attr.class]': '"contents"' },
      imports: [
        LucideAngularModule,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiCardDescriptionComponent,
        UiCardHeaderComponent,
        UiCardTitleComponent,
        UiProgressComponent,
        UiSelectComponent,
        UiSelectContentComponent,
        UiSelectItemComponent,
        UiSelectTriggerComponent,
        UiSelectValueComponent,
        UiSeparatorComponent,
        UiTableBodyComponent,
        UiTableCellComponent,
        UiTableComponent,
        UiTableHeadComponent,
        UiTableHeaderComponent,
        UiTableRowComponent,
      ],
      template: `
        <div data-slot="compensation-band-benchmarking" [class]="rootClass">
          <!-- Main Header Card -->
          <div ui-card class="border-border bg-card shadow-xs">
            <div ui-card-content class="p-6">
              <div class="flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between">
                <div class="space-y-1.5">
                  <div class="flex flex-wrap items-center gap-2.5">
                    <h2 class="text-foreground text-xl font-bold tracking-tight sm:text-2xl">
                      Engineering Compensation Bands & Market Benchmarks
                    </h2>
                    <span ui-badge wrap variant="success" class="gap-1 font-semibold shadow-xs">
                      <lucide-icon [img]="BadgeCheck" class="text-success size-3.5" />
                      <span>Q3 2026 Live Bands</span>
                    </span>
                  </div>
                  <p class="text-muted-foreground text-sm">
                    Pave Market Data · SF Bay Area Tier 1 · Q3 2026 (Refreshed Aug 2026 · Sample n = 42,400 Tech ICs)
                  </p>
                  <div class="flex flex-wrap items-center gap-3 pt-1">
                    <span ui-badge wrap variant="outline" class="gap-1.5 text-xs font-normal">
                      <lucide-icon [img]="Building2" class="text-primary size-3.5" />
                      <span>Benchmark Market: San Francisco Bay Area (Tier 1)</span>
                    </span>
                    <ui-separator orientation="vertical" class="hidden h-3.5 sm:block" />
                    <span class="text-muted-foreground text-xs">
                      Currency: <strong class="text-foreground font-medium">USD ($) · Annualized</strong>
                    </span>
                    <ui-separator orientation="vertical" class="hidden h-3.5 sm:block" />
                    <span class="text-muted-foreground text-xs">
                      Total Engineers Tracked:
                      <strong class="text-foreground font-medium tabular-nums">101 Headcount</strong>
                    </span>
                  </div>
                </div>
    
                <div class="flex flex-col gap-3 sm:flex-row sm:items-center">
                  <div class="w-full sm:w-64">
                    <label for="role-select" class="text-muted-foreground mb-1 block text-xs font-medium">
                      Focus Benchmark Level:
                    </label>
                    <ui-select [value]="selectedRoleId()" (valueChange)="setSelectedRole($event)">
                      <button ui-select-trigger id="role-select" class="bg-background h-9 w-full text-xs font-medium">
                        <ui-select-value placeholder="Select engineering role" />
                      </button>
                      <ui-select-content>
                        @for (lvl of levels; track lvl.id) {
                          <ui-select-item [value]="lvl.id" class="text-xs">{{ lvl.roleTitle }}</ui-select-item>
                        }
                      </ui-select-content>
                    </ui-select>
                  </div>
    
                  <div class="sm:self-end">
                    <button
                      ui-button
                      aria-label="Download attachment"
                      variant="outline"
                      size="sm"
                      class="h-9 w-full gap-2 shadow-xs sm:w-auto"
                      (click)="exportCsv()"
                    >
                      <lucide-icon [img]="Download" class="size-4" />
                      <span>Export Bands CSV</span>
                    </button>
                  </div>
                </div>
              </div>
            </div>
          </div>
    
          <!-- 4 Compensation Summary Cards -->
          <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
            <!-- 1. Median Base Salary -->
            <div ui-card class="border-border bg-card flex flex-col justify-between shadow-xs">
              <div ui-card-header class="pb-2">
                <div class="flex flex-wrap items-center justify-between gap-2">
                  <div class="flex min-w-0 items-center gap-2">
                    <div
                      class="bg-muted border-border flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs"
                      aria-hidden="true"
                    >
                      <lucide-icon [img]="DollarSign" class="text-primary size-4" />
                    </div>
                    <h3 ui-card-title class="text-muted-foreground truncate text-xs font-medium">Median Base Salary</h3>
                  </div>
                  <span ui-badge wrap variant="outline" class="shrink-0 text-xs font-medium tabular-nums">
                    {{ activeLevel().code }} · P50 Market
                  </span>
                </div>
              </div>
              <div ui-card-content class="space-y-3 pt-1">
                <div>
                  <div class="mb-1 flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5">
                    <div class="flex items-baseline gap-1.5">
                      <span class="text-foreground text-2xl font-bold tracking-tight tabular-nums sm:text-3xl">
                        {{ fmtCurrency(activeLevel().p50Base) }}
                      </span>
                      <span class="text-muted-foreground text-xs font-normal">/ yr</span>
                    </div>
                    <div class="text-success flex items-center gap-1 text-xs font-medium">
                      <lucide-icon [img]="TrendingUp" class="size-3" />
                      <span class="tabular-nums">+2.3% Pave P50</span>
                    </div>
                  </div>
                  <ui-progress [value]="getPercent(activeLevel().p50Base)" class="h-1.5 w-full" />
                </div>
                <div class="border-border/60 border-t pt-2.5 text-xs">
                  <div class="text-muted-foreground flex items-center justify-between">
                    <span>Band Range (P25–P90):</span>
                    <span class="text-foreground font-semibold tabular-nums">
                      {{ fmtK(activeLevel().p25Base) }} – {{ fmtK(activeLevel().p90Base) }}
                    </span>
                  </div>
                  <p class="text-muted-foreground mt-1 line-clamp-1 text-xs">
                    Market baseline for {{ activeLevel().title }} with {{ activeLevel().experience }}.
                  </p>
                </div>
              </div>
            </div>
    
            <!-- 2. Median Annual Equity / RSU -->
            <div ui-card class="border-border bg-card flex flex-col justify-between shadow-xs">
              <div ui-card-header class="pb-2">
                <div class="flex flex-wrap items-center justify-between gap-2">
                  <div class="flex min-w-0 items-center gap-2">
                    <div
                      class="bg-muted border-border flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs"
                      aria-hidden="true"
                    >
                      <lucide-icon [img]="Coins" class="text-info size-4" />
                    </div>
                    <h3 ui-card-title class="text-muted-foreground truncate text-xs font-medium">
                      Median Annual Equity / RSU
                    </h3>
                  </div>
                  <span ui-badge wrap variant="secondary" class="shrink-0 text-xs font-medium tabular-nums">
                    {{ activeLevel().equityPercent }}% Total Comp
                  </span>
                </div>
              </div>
              <div ui-card-content class="space-y-3 pt-1">
                <div>
                  <div class="mb-1 flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5">
                    <div class="flex items-baseline gap-1.5">
                      <span class="text-foreground text-2xl font-bold tracking-tight tabular-nums sm:text-3xl">
                        {{ fmtCurrency(activeLevel().equityP50) }}
                      </span>
                      <span class="text-muted-foreground text-xs font-normal">/ yr</span>
                    </div>
                    <div class="text-muted-foreground flex items-center gap-1 text-xs font-medium">
                      <span class="tabular-nums">4-Yr Vesting</span>
                    </div>
                  </div>
                  <ui-progress
                    [value]="activeLevel().equityPercent"
                    class="[&_[data-slot=progress-indicator]]:bg-info h-1.5 w-full"
                  />
                </div>
                <div class="border-border/60 border-t pt-2.5 text-xs">
                  <div class="text-muted-foreground flex items-center justify-between">
                    <span>Annual Grant Band:</span>
                    <span class="text-foreground font-semibold tabular-nums">
                      {{ fmtK(activeLevel().equityMin) }} – {{ fmtK(activeLevel().equityMax) }} / yr
                    </span>
                  </div>
                  <p class="text-muted-foreground mt-1 line-clamp-1 text-xs">
                    Liquid RSU allocation (4-year vest, 1-year standard cliff).
                  </p>
                </div>
              </div>
            </div>
    
            <!-- 3. Target Total Compensation -->
            <div ui-card class="border-border bg-card flex flex-col justify-between shadow-xs">
              <div ui-card-header class="pb-2">
                <div class="flex flex-wrap items-center justify-between gap-2">
                  <div class="flex min-w-0 items-center gap-2">
                    <div
                      class="bg-muted border-border flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs"
                      aria-hidden="true"
                    >
                      <lucide-icon [img]="Award" class="text-chart-1 size-4" />
                    </div>
                    <h3 ui-card-title class="text-muted-foreground truncate text-xs font-medium">
                      Target Total Compensation
                    </h3>
                  </div>
                  <span ui-badge wrap variant="outline" class="shrink-0 text-xs font-medium">Base + Equity</span>
                </div>
              </div>
              <div ui-card-content class="space-y-3 pt-1">
                <div>
                  <div class="mb-1 flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5">
                    <div class="flex items-baseline gap-1.5">
                      <span class="text-foreground text-2xl font-bold tracking-tight tabular-nums sm:text-3xl">
                        {{ fmtCurrency(activeLevel().totalP50) }}
                      </span>
                      <span class="text-muted-foreground text-xs font-normal">/ yr</span>
                    </div>
                    <div class="text-success flex items-center gap-1 text-xs font-medium">
                      <lucide-icon [img]="TrendingUp" class="size-3" />
                      <span class="tabular-nums">+2.5% vs Mkt</span>
                    </div>
                  </div>
                  <ui-progress
                    [value]="getPercent(activeLevel().totalP50)"
                    class="[&_[data-slot=progress-indicator]]:bg-chart-1 h-1.5 w-full"
                  />
                </div>
                <div class="border-border/60 border-t pt-2.5 text-xs">
                  <div class="text-muted-foreground flex items-center justify-between">
                    <span>Target Total Comp Range:</span>
                    <span class="text-foreground font-semibold tabular-nums">
                      {{ fmtK(activeLevel().totalMin) }} – {{ fmtK(activeLevel().totalMax) }}
                    </span>
                  </div>
                  <p class="text-muted-foreground mt-1 line-clamp-1 text-xs">
                    Annualized total compensation at P50 target execution.
                  </p>
                </div>
              </div>
            </div>
    
            <!-- 4. Internal Compa-Ratio -->
            <div ui-card class="border-border bg-card flex flex-col justify-between shadow-xs">
              <div ui-card-header class="pb-2">
                <div class="flex flex-wrap items-center justify-between gap-2">
                  <div class="flex min-w-0 items-center gap-2">
                    <div
                      class="bg-muted border-border flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs"
                      aria-hidden="true"
                    >
                      <lucide-icon [img]="Scale" class="text-success size-4" />
                    </div>
                    <h3 ui-card-title class="text-muted-foreground truncate text-xs font-medium">Internal Compa-Ratio</h3>
                  </div>
                  <span ui-badge wrap variant="success" class="shrink-0 text-xs font-semibold">Market Competitive</span>
                </div>
              </div>
              <div ui-card-content class="space-y-3 pt-1">
                <div>
                  <div class="mb-1 flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5">
                    <div class="flex items-baseline gap-1.5">
                      <span class="text-foreground text-2xl font-bold tracking-tight tabular-nums sm:text-3xl">
                        {{ activeLevel().compaRatio }}%
                      </span>
                      <span class="text-muted-foreground text-xs font-normal">Compa</span>
                    </div>
                    <div class="text-success flex items-center gap-1 text-xs font-medium">
                      <lucide-icon [img]="CheckCircle2" class="size-3.5" />
                      <span>Target Corridor</span>
                    </div>
                  </div>
                  <ui-progress
                    [value]="activeLevel().compaRatio - 50"
                    class="[&_[data-slot=progress-indicator]]:bg-success h-1.5 w-full"
                  />
                </div>
                <div class="border-border/60 border-t pt-2.5 text-xs">
                  <div class="text-muted-foreground flex items-center justify-between">
                    <span>Target Standard:</span>
                    <span class="text-foreground font-semibold tabular-nums">95.0% – 105.0%</span>
                  </div>
                  <p class="text-muted-foreground mt-1 line-clamp-1 text-xs">
                    Pay positioning calibrated strictly inside safe competitive envelope.
                  </p>
                </div>
              </div>
            </div>
          </div>
    
          <!-- Level Progression Compensation Matrix -->
          <div ui-card class="border-border bg-card shadow-xs">
            <div ui-card-header class="pb-4">
              <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
                <div>
                  <div class="flex items-center gap-2">
                    <lucide-icon [img]="Layers" class="text-primary size-5" />
                    <h3 ui-card-title class="text-base font-semibold">Level Progression Compensation Matrix</h3>
                  </div>
                  <p ui-card-description class="text-xs">
                    Multi-bar percentile bands (P25 → P50 → P75 → P90) and equity allocations across 5 engineering career
                    levels.
                  </p>
                </div>
                <div class="flex flex-wrap items-center gap-2">
                  <span ui-badge wrap variant="outline" class="text-xs font-normal tabular-nums">
                    Scale: $100k → $420k Base
                  </span>
                  <span ui-badge wrap variant="secondary" class="text-xs font-medium tabular-nums">
                    5 Levels · 101 Engineers
                  </span>
                </div>
              </div>
            </div>
            <div ui-card-content class="space-y-4">
              <div class="bg-muted/40 border-border/80 hidden rounded-lg border p-3 md:block">
                <div class="flex items-center justify-between text-xs font-medium">
                  <span class="text-muted-foreground">Base Salary Percentile Scale Guide:</span>
                  <div class="flex items-center gap-4 text-xs">
                    <div class="flex items-center gap-1.5">
                      <span class="border-primary/40 bg-primary/20 size-2.5 rounded-sm border"></span>
                      <span class="text-muted-foreground">P25–P90 Band Corridor</span>
                    </div>
                    <div class="flex items-center gap-1.5">
                      <span class="bg-success ring-success/50 size-2.5 rounded-full ring-1"></span>
                      <span class="text-foreground font-semibold">P50 Median Market Point</span>
                    </div>
                  </div>
                </div>
                <div class="border-border/60 relative mt-2.5 h-4 border-t pt-1">
                  <div class="text-muted-foreground flex justify-between text-xs tabular-nums">
                    <span>$100k</span>
                    <span>$150k</span>
                    <span>$200k</span>
                    <span>$250k</span>
                    <span>$300k</span>
                    <span>$350k</span>
                    <span>$400k+</span>
                  </div>
                </div>
              </div>
    
              <div class="border-border overflow-hidden rounded-lg border">
                <div class="overflow-x-auto">
                  <table ui-table>
                    <thead ui-table-header class="bg-muted/50">
                      <tr ui-table-row>
                        <th ui-table-head class="w-48 text-xs font-medium">Level & Role Scope</th>
                        <th ui-table-head class="min-w-[280px] text-xs font-medium">
                          Base Salary Band (P25 → P50 → P75 → P90)
                        </th>
                        <th ui-table-head class="w-40 text-xs font-medium">Annual RSU Grant</th>
                        <th ui-table-head class="w-44 text-xs font-medium">Target Total Comp (TTC)</th>
                        <th ui-table-head class="w-36 text-right text-xs font-medium">Headcount & Compa</th>
                      </tr>
                    </thead>
                    <tbody ui-table-body>
                      @for (lvl of levels; track lvl.id) {
                        <tr
                          ui-table-row
                          (click)="setSelectedRole(lvl.id)"
                          [class]="rowClass(lvl.id)"
                        >
                          <td ui-table-cell class="py-4 align-top">
                            <div class="space-y-1">
                              <div class="flex items-center gap-2">
                                <span ui-badge wrap [variant]="lvl.badgeVariant" class="font-bold tabular-nums">
                                  {{ lvl.code }}
                                </span>
                                <span class="text-foreground text-sm font-semibold">{{ lvl.title }}</span>
                              </div>
                              <p class="text-muted-foreground text-xs">{{ lvl.experience }}</p>
                              <p class="text-muted-foreground line-clamp-1 text-xs">{{ lvl.scope }}</p>
                            </div>
                          </td>
                          <td ui-table-cell class="py-4 align-middle">
                            <div class="space-y-2 py-1">
                              <div
                                class="bg-muted border-border/80 relative h-4 w-full overflow-hidden rounded-full border"
                              >
                                <div
                                  class="bg-primary/25 border-primary/40 absolute inset-y-0 rounded-full border"
                                  [style.left.%]="getPercent(lvl.p25Base)"
                                  [style.width.%]="getPercent(lvl.p90Base) - getPercent(lvl.p25Base)"
                                ></div>
                                <div
                                  class="bg-primary/30 absolute inset-y-0"
                                  [style.left.%]="getPercent(lvl.p50Base)"
                                  [style.width.%]="getPercent(lvl.p75Base) - getPercent(lvl.p50Base)"
                                ></div>
                                <div
                                  class="bg-foreground/40 absolute top-0 bottom-0 w-0.5"
                                  [style.left.%]="getPercent(lvl.p25Base)"
                                  title="P25 Percentile"
                                ></div>
                                <div
                                  class="border-background bg-success ring-success/40 absolute top-1/2 size-3 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 shadow-xs ring-2"
                                  [style.left.%]="getPercent(lvl.p50Base)"
                                  title="P50 Median"
                                ></div>
                                <div
                                  class="bg-foreground/40 absolute top-0 bottom-0 w-0.5"
                                  [style.left.%]="getPercent(lvl.p75Base)"
                                  title="P75 Percentile"
                                ></div>
                                <div
                                  class="bg-foreground/40 absolute top-0 bottom-0 w-0.5"
                                  [style.left.%]="getPercent(lvl.p90Base)"
                                  title="P90 Percentile"
                                ></div>
                              </div>
                              <div class="text-muted-foreground flex items-center justify-between text-xs tabular-nums">
                                <span>
                                  P25: <strong class="text-foreground">{{ fmtK(lvl.p25Base) }}</strong>
                                </span>
                                <span class="text-success font-bold">P50: {{ fmtK(lvl.p50Base) }}</span>
                                <span>
                                  P75: <strong class="text-foreground">{{ fmtK(lvl.p75Base) }}</strong>
                                </span>
                                <span>
                                  P90: <strong class="text-foreground">{{ fmtK(lvl.p90Base) }}</strong>
                                </span>
                              </div>
                            </div>
                          </td>
                          <td ui-table-cell class="py-4 align-middle">
                            <div class="space-y-0.5">
                              <p class="text-foreground text-sm font-semibold tabular-nums">
                                {{ fmtK(lvl.equityMin) }} – {{ fmtK(lvl.equityMax) }}
                              </p>
                              <p class="text-muted-foreground text-xs tabular-nums">
                                P50: {{ fmtCurrency(lvl.equityP50) }} / yr
                              </p>
                            </div>
                          </td>
                          <td ui-table-cell class="py-4 align-middle">
                            <div class="space-y-0.5">
                              <p class="text-foreground text-sm font-semibold tabular-nums">
                                {{ fmtK(lvl.totalMin) }} – {{ fmtK(lvl.totalMax) }}
                              </p>
                              <p class="text-success text-xs font-medium tabular-nums">
                                Target P50: {{ fmtCurrency(lvl.totalP50) }}
                              </p>
                            </div>
                          </td>
                          <td ui-table-cell class="py-4 text-right align-middle">
                            <div class="flex flex-col items-end gap-1">
                              <div class="flex items-center gap-1.5 text-xs">
                                <lucide-icon [img]="Users" class="text-muted-foreground size-3.5" />
                                <span class="text-foreground font-medium tabular-nums">{{ lvl.headcount }} engineers</span>
                              </div>
                              <span ui-badge wrap variant="success" class="text-xs font-semibold tabular-nums">
                                {{ lvl.compaRatio }}% Compa
                              </span>
                            </div>
                          </td>
                        </tr>
                      }
                    </tbody>
                  </table>
                </div>
              </div>
            </div>
          </div>
    
          <!-- Pay Equity & Fair Pay Audit Card -->
          <div ui-card class="border-border bg-card shadow-xs">
            <div ui-card-header class="pb-4">
              <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
                <div class="space-y-1">
                  <div class="flex items-center gap-2">
                    <lucide-icon [img]="ShieldCheck" class="text-success size-5" />
                    <h3 ui-card-title class="text-base font-semibold">Pay Equity & Fair Pay Audit Certification</h3>
                  </div>
                  <p ui-card-description class="text-xs">
                    Independent statistical regression analysis evaluating compensation equity across gender, demographics,
                    and level cohorts.
                  </p>
                </div>
                <span ui-badge wrap variant="success" class="gap-1.5 px-3 py-1 text-xs font-semibold shadow-xs">
                  <lucide-icon [img]="Award" class="text-success size-3.5" />
                  <span>99.8% Parity Ratio · Certified Fair Pay</span>
                </span>
              </div>
            </div>
            <div ui-card-content class="space-y-6">
              <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
                <div class="bg-muted/40 border-border/80 space-y-1 rounded-lg border p-3.5">
                  <div class="text-muted-foreground flex items-center justify-between text-xs">
                    <span class="font-medium">Gender Pay Parity</span>
                    <span ui-badge wrap variant="outline" class="text-success text-xs font-semibold">100% Target</span>
                  </div>
                  <p class="text-foreground text-2xl font-bold tracking-tight tabular-nums">99.8%</p>
                  <p class="text-muted-foreground text-xs">Female-to-male comp ratio for equal job level and role scope.</p>
                </div>
                <div class="bg-muted/40 border-border/80 space-y-1 rounded-lg border p-3.5">
                  <div class="text-muted-foreground flex items-center justify-between text-xs">
                    <span class="font-medium">Demographic & URG Parity</span>
                    <span ui-badge wrap variant="outline" class="text-success text-xs font-semibold">Parity Achieved</span>
                  </div>
                  <p class="text-foreground text-2xl font-bold tracking-tight tabular-nums">100.1%</p>
                  <p class="text-muted-foreground text-xs">Statistical comp ratio for underrepresented group cohorts.</p>
                </div>
                <div class="bg-muted/40 border-border/80 space-y-1 rounded-lg border p-3.5">
                  <div class="text-muted-foreground flex items-center justify-between text-xs">
                    <span class="font-medium">In-Band Compliance</span>
                    <span ui-badge wrap variant="outline" class="text-success text-xs font-semibold">High Precision</span>
                  </div>
                  <p class="text-foreground text-2xl font-bold tracking-tight tabular-nums">97.2%</p>
                  <p class="text-muted-foreground text-xs">
                    Employees placed strictly between approved P25 and P90 boundaries.
                  </p>
                </div>
                <div class="bg-muted/40 border-border/80 space-y-1 rounded-lg border p-3.5">
                  <div class="text-muted-foreground flex items-center justify-between text-xs">
                    <span class="font-medium">Compression Gini Index</span>
                    <span ui-badge wrap variant="outline" class="text-success text-xs font-semibold">Low Risk</span>
                  </div>
                  <p class="text-foreground text-2xl font-bold tracking-tight tabular-nums">0.11</p>
                  <p class="text-muted-foreground text-xs">
                    Healthy inter-level step progression with zero pay overlap anomalies.
                  </p>
                </div>
              </div>
    
              <div class="bg-muted/30 border-border space-y-3 rounded-lg border p-4">
                <div class="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between">
                  <span class="text-foreground text-xs font-semibold">Equal Pay Compliance Corridor (95.0% – 105.0%)</span>
                  <span class="text-muted-foreground text-xs">
                    Audit Status: <strong class="text-success font-semibold">Certified Safe Harbor (0.2% Variance)</strong>
                  </span>
                </div>
                <div class="bg-muted border-border/80 relative h-3.5 w-full overflow-hidden rounded-full border">
                  <div class="bg-warning/20 absolute inset-y-0 left-0 w-[30%]"></div>
                  <div class="bg-success/30 absolute inset-y-0 left-[30%] w-[40%]"></div>
                  <div class="bg-warning/20 absolute inset-y-0 right-0 w-[30%]"></div>
                  <div
                    class="border-background bg-success ring-success/50 absolute top-1/2 size-3.5 -translate-y-1/2 rounded-full border-2 shadow-xs ring-2"
                    style="left: 48%"
                    title="Current Company Parity: 99.8%"
                  ></div>
                </div>
                <div class="text-muted-foreground flex items-center justify-between text-xs tabular-nums">
                  <span>95.0% (Underpaid Threshold)</span>
                  <span class="text-success font-bold">99.8% (Current Company Parity)</span>
                  <span>100.0% (True Parity)</span>
                  <span>105.0% (Overpaid Threshold)</span>
                </div>
              </div>
    
              <div
                class="border-border/60 flex flex-col gap-3 border-t pt-4 text-xs sm:flex-row sm:items-center sm:justify-between"
              >
                <div class="text-muted-foreground flex items-center gap-2">
                  <lucide-icon [img]="CheckCircle2" class="text-success size-4 shrink-0" />
                  <span>
                    Certified by Independent PayParity Institute · Audit ID:
                    <strong class="text-foreground font-mono font-medium">#EQ-2026-SF-8841</strong>
                  </span>
                </div>
                <span class="text-muted-foreground">
                  Next Audit Recertification Scheduled: <strong class="text-foreground">July 2027</strong>
                </span>
              </div>
            </div>
          </div>
        </div>
      `,
    })
    export class UiCompensationBandBenchmarkingComponent {
      protected readonly Award = Award
      protected readonly BadgeCheck = BadgeCheck
      protected readonly Building2 = Building2
      protected readonly CheckCircle2 = CheckCircle2
      protected readonly Coins = Coins
      protected readonly DollarSign = DollarSign
      protected readonly Download = Download
      protected readonly Layers = Layers
      protected readonly Scale = Scale
      protected readonly ShieldCheck = ShieldCheck
      protected readonly TrendingUp = TrendingUp
      protected readonly Users = Users
    
      @Input() levels: CompensationLevel[] = defaultLevels
      @Input() set initialRole(v: string | undefined) {
        if (v) this.selectedRoleId.set(v)
      }
      @Input('class') className?: string
    
      readonly selectedRoleId = signal('ic5')
    
      readonly activeLevel = computed(() => {
        return this.levels.find((l) => l.id === this.selectedRoleId()) ?? this.levels[2]
      })
    
      get rootClass(): string {
        return cn('w-full space-y-6', this.className)
      }
    
      setSelectedRole(v: string): void {
        if (v) this.selectedRoleId.set(v)
      }
    
      rowClass(id: string): string {
        return cn(
          'cursor-pointer transition-colors',
          this.selectedRoleId() === id && 'bg-primary/5 dark:bg-primary/10 font-medium',
        )
      }
    
      getPercent(val: number): number {
        return getPercent(val)
      }
    
      fmtCurrency(val: number): string {
        return fmtCurrency(val)
      }
    
      fmtK(val: number): string {
        return fmtK(val)
      }
    
      exportCsv(): void {
        const headers = [
          'Level Code',
          'Level Title',
          'Experience Scope',
          'Headcount',
          'P25 Base Salary',
          'P50 Base Salary (Median)',
          'P75 Base Salary',
          'P90 Base Salary',
          'Equity Range Min',
          'Equity Range Max',
          'Median Annual Equity',
          'Total Comp Min',
          'Total Comp Max',
          'Target Total Comp (P50)',
          'Compa-Ratio (%)',
        ]
        const rows = this.levels.map((l) => [
          l.code,
          `"${l.title}"`,
          `"${l.experience}"`,
          l.headcount,
          l.p25Base,
          l.p50Base,
          l.p75Base,
          l.p90Base,
          l.equityMin,
          l.equityMax,
          l.equityP50,
          l.totalMin,
          l.totalMax,
          l.totalP50,
          l.compaRatio,
        ])
        const csvContent = 'data:text/csv;charset=utf-8,' + [headers.join(','), ...rows.map((e) => e.join(','))].join('\n')
        const encodedUri = encodeURI(csvContent)
        const link = document.createElement('a')
        link.setAttribute('href', encodedUri)
        link.setAttribute('download', 'engineering-compensation-bands-q3-2026.csv')
        document.body.appendChild(link)
        link.click()
        document.body.removeChild(link)
      }
    }
    
  • src/app/components/blocks/compensation-band-benchmarking/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/compensation-band-benchmarking.json