Compensation Band Benchmarking
blockhrPave/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
$pnpm dlx uipkge-ng@latest add compensation-band-benchmarking$npx uipkge-ng@latest add compensation-band-benchmarking$yarn dlx uipkge-ng@latest add compensation-band-benchmarking$bunx uipkge-ng@latest add compensation-band-benchmarkingManifest:
https://uipkge.dev/r/angular/compensation-band-benchmarking.jsonInstalls to:src/app/components/blocks/compensation-band-benchmarking/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
CompensationLevelinterface 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
export { UiCompensationBandBenchmarkingComponent, type CompensationLevel, } from './compensation-band-benchmarking.component'
Raw manifest:https://uipkge.dev/r/angular/compensation-band-benchmarking.json