Menu

Change language

GitHub repo

Cap Table Ownership Summary

blockfinance

Carta-style company equity capitalization table, share class breakdown, fully diluted ownership percentages, shareholder vesting progress, and interactive round dilution modeling simulator.

Installation

$npx uipkge-ng@latest add cap-table-ownership-summary
Manifest:https://uipkge.dev/r/angular/cap-table-ownership-summary.jsonInstalls to:src/app/components/blocks/cap-table-ownership-summary/

Variants

Loading interactive previews…

Schema

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

Shareholder
interface Shareholder {
  id: string
  name: string
  role: string
  avatar: string
  shareClass: CapTableShareClass
  shares: number
  ownershipPct: number
  equityValue: number
  vestingDetail: string
  vestedMonths: number
  totalMonths: number
  isVestedComplete: boolean
  grantDate: string
  liquidationPref?: string
}

Files installed (2)

  • src/app/components/blocks/cap-table-ownership-summary/cap-table-ownership-summary.component.ts55.7 kB
    // Carta-style company equity capitalization table, share class breakdown, fully diluted
    // ownership percentages, shareholder vesting progress, and an interactive round dilution
    // modeling simulator. Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'
    import {
      Award,
      Building2,
      Calculator,
      Check,
      Coins,
      FileSpreadsheet,
      Info,
      Layers,
      LucideAngularModule,
      Plus,
      RefreshCw,
      Search,
      ShieldCheck,
      TrendingUp,
      Users,
      X,
    } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiAvatarComponent, UiAvatarFallbackComponent } from '@/ui/avatar/avatar.component'
    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 { UiInputComponent } from '@/ui/input/input.component'
    import {
      UiTableBodyComponent,
      UiTableCellComponent,
      UiTableComponent,
      UiTableHeadComponent,
      UiTableHeaderComponent,
      UiTableRowComponent,
    } from '@/ui/table/table.component'
    
    export type CapTableShareClass = 'Common Stock' | 'Series Seed Preferred' | 'ISO Option Pool'
    export type CapTableClassFilter = 'All' | CapTableShareClass
    export type GrantEquityClass = 'ISO Options' | 'NSO Options' | 'Restricted Stock'
    
    export interface Shareholder {
      id: string
      name: string
      role: string
      avatar: string
      shareClass: CapTableShareClass
      shares: number
      ownershipPct: number
      equityValue: number
      vestingDetail: string
      vestedMonths: number
      totalMonths: number
      isVestedComplete: boolean
      grantDate: string
      liquidationPref?: string
    }
    
    const INITIAL_SHAREHOLDERS: Shareholder[] = [
      {
        id: 'sh-1',
        name: 'Elena Rostova',
        role: 'Co-Founder & CEO',
        avatar: 'ER',
        shareClass: 'Common Stock',
        shares: 3500000,
        ownershipPct: 35.0,
        equityValue: 15750000,
        vestingDetail: '48-Month Vesting · 24/48 Months Vested',
        vestedMonths: 24,
        totalMonths: 48,
        isVestedComplete: false,
        grantDate: 'Feb 15, 2024 · 1-yr Cliff (Satisfied)',
      },
      {
        id: 'sh-2',
        name: 'Marcus Vance',
        role: 'Co-Founder & CTO',
        avatar: 'MV',
        shareClass: 'Common Stock',
        shares: 2500000,
        ownershipPct: 25.0,
        equityValue: 11250000,
        vestingDetail: '48-Month Vesting · 24/48 Months Vested',
        vestedMonths: 24,
        totalMonths: 48,
        isVestedComplete: false,
        grantDate: 'Feb 15, 2024 · 1-yr Cliff (Satisfied)',
      },
      {
        id: 'sh-3',
        name: 'Founders Fund / Seed Syndicate',
        role: 'Lead Seed Investor',
        avatar: 'FF',
        shareClass: 'Series Seed Preferred',
        shares: 1500000,
        ownershipPct: 15.0,
        equityValue: 6750000,
        vestingDetail: 'Fully Vested (100%) · 1.0x Non-Participating',
        vestedMonths: 48,
        totalMonths: 48,
        isVestedComplete: true,
        grantDate: 'Aug 10, 2024 · Board Seat (1)',
        liquidationPref: '1.0x Pref ($6.75M)',
      },
      {
        id: 'sh-4',
        name: 'Sequoia Scout Seed SPV',
        role: 'Seed Co-Investor',
        avatar: 'SQ',
        shareClass: 'Series Seed Preferred',
        shares: 1000000,
        ownershipPct: 10.0,
        equityValue: 4500000,
        vestingDetail: 'Fully Vested (100%) · 1.0x Non-Participating',
        vestedMonths: 48,
        totalMonths: 48,
        isVestedComplete: true,
        grantDate: 'Aug 10, 2024 · Pro-Rata Rights',
        liquidationPref: '1.0x Pref ($4.50M)',
      },
      {
        id: 'sh-5',
        name: 'Unallocated Employee Option Pool',
        role: '2024 Equity Incentive Plan (EIP)',
        avatar: 'EP',
        shareClass: 'ISO Option Pool',
        shares: 1500000,
        ownershipPct: 15.0,
        equityValue: 6750000,
        vestingDetail: 'Authorized Pool · Available for Future Key Hires',
        vestedMonths: 0,
        totalMonths: 48,
        isVestedComplete: false,
        grantDate: 'Board Authorized · Dec 01, 2024',
      },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-cap-table-ownership-summary, [ui-cap-table-ownership-summary]',
      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,
        UiAvatarComponent,
        UiAvatarFallbackComponent,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiCardDescriptionComponent,
        UiCardHeaderComponent,
        UiCardTitleComponent,
        UiInputComponent,
        UiTableBodyComponent,
        UiTableCellComponent,
        UiTableComponent,
        UiTableHeadComponent,
        UiTableHeaderComponent,
        UiTableRowComponent,
      ],
      template: `
        <div data-slot="cap-table-ownership-summary" [class]="rootClass">
          <!-- Top Header -->
          <header class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
            <div class="space-y-1.5">
              <div class="flex flex-wrap items-center gap-2.5">
                <div
                  class="bg-primary/10 text-primary border-primary/20 flex size-9 shrink-0 items-center justify-center rounded-lg border shadow-xs"
                >
                  <lucide-icon [img]="Building2" class="size-5" />
                </div>
                <div>
                  <h1 class="text-foreground text-2xl font-bold tracking-tight">
                    Capitalization Table &amp; Equity Ownership
                  </h1>
                </div>
                <span ui-badge variant="outline" class="border-success/30 bg-success/10 text-success gap-1.5 font-normal">
                  <span class="bg-success size-1.5 animate-pulse rounded-full"></span>
                  Audited · Series Seed
                </span>
              </div>
    
              <div class="text-muted-foreground flex flex-wrap items-center gap-2 text-xs">
                <span class="bg-muted/70 text-foreground border-border/80 rounded border px-2 py-0.5 font-medium">
                  UIPKGE Technologies Inc. · Delaware C-Corp
                </span>
                <span>EIN: 93-8472910</span>
                <span class="inline-flex items-center gap-1 font-mono">
                  <lucide-icon [img]="ShieldCheck" class="text-primary size-3.5" />
                  409A FMV: &#36;{{ currentSharePrice.toFixed(2) }}/share
                </span>
              </div>
            </div>
    
            <!-- Action Buttons -->
            <div class="flex flex-wrap items-center gap-2">
              <button
                ui-button
                variant="outline"
                size="sm"
                class="gap-1.5 shadow-xs"
                [disabled]="isExporting()"
                (click)="handleExportCapTable()"
              >
                @if (isExporting()) {
                  <lucide-icon [img]="RefreshCw" class="size-4 animate-spin" />
                } @else if (exportSuccess()) {
                  <lucide-icon [img]="Check" class="text-success size-4" />
                } @else {
                  <lucide-icon [img]="FileSpreadsheet" class="size-4" />
                }
                <span>{{ exportButtonLabel() }}</span>
              </button>
    
              <button ui-button size="sm" class="gap-1.5 shadow-xs" (click)="isGrantDialogOpen.set(true)">
                <lucide-icon [img]="Plus" class="size-4" />
                Issue Equity Grant
              </button>
            </div>
          </header>
    
          <!-- Issue Equity Grant Dialog Modal Overlay -->
          @if (isGrantDialogOpen()) {
            <div
              class="bg-background/80 fixed inset-0 z-50 flex items-center justify-center p-4 backdrop-blur-xs"
              role="dialog"
              aria-modal="true"
              aria-labelledby="grant-dialog-title"
            >
              <div ui-card class="border-border w-full max-w-lg shadow-lg">
                <div ui-card-header class="pb-3">
                  <div class="flex flex-wrap items-center justify-between gap-x-2">
                    <h2 ui-card-title id="grant-dialog-title" class="flex items-center gap-2 text-base font-semibold">
                      <lucide-icon [img]="Award" class="text-primary size-5" />
                      Issue New Equity Option Grant
                    </h2>
                    <button
                      ui-button
                      variant="ghost"
                      size="icon-sm"
                      class="text-muted-foreground hover:text-foreground size-7"
                      aria-label="Close dialog"
                      (click)="isGrantDialogOpen.set(false)"
                    >
                      <lucide-icon [img]="X" class="size-4" />
                    </button>
                  </div>
                  <p ui-card-description class="text-xs">
                    Authorize and draft an option grant under the 2024 Equity Incentive Plan (EIP).
                  </p>
                </div>
    
                <div ui-card-content class="space-y-4">
                  @if (grantSuccessMessage()) {
                    <div class="border-success/20 bg-success/10 rounded-lg border p-4 text-center">
                      <lucide-icon [img]="Check" class="text-success mx-auto size-6" />
                      <p class="text-foreground mt-2 text-sm font-semibold">Option Grant Issued &amp; Drafted!</p>
                      <p class="text-muted-foreground mt-0.5 text-xs">
                        Sent to Board of Directors for electronic consent signature.
                      </p>
                    </div>
                  } @else {
                    <form class="space-y-3.5" (submit)="handleCreateGrant($event)">
                      <div class="space-y-1.5">
                        <label for="angular-grantee-name" class="text-foreground text-xs font-medium">
                          Grantee Legal Name
                        </label>
                        <ui-input
                          id="angular-grantee-name"
                          [value]="grantName()"
                          (valueChange)="grantName.set($event)"
                          placeholder="e.g. Dr. Sarah Chen"
                          class="h-9 text-xs"
                        />
                      </div>
    
                      <div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
                        <div class="space-y-1.5">
                          <label for="angular-grantee-role" class="text-foreground text-xs font-medium">Role / Title</label>
                          <ui-input
                            id="angular-grantee-role"
                            [value]="grantRole()"
                            (valueChange)="grantRole.set($event)"
                            placeholder="e.g. Principal AI Scientist"
                            class="h-9 text-xs"
                          />
                        </div>
    
                        <div class="space-y-1.5">
                          <label for="angular-grant-quantity" class="text-foreground text-xs font-medium">
                            Number of Options / Shares
                          </label>
                          <ui-input
                            id="angular-grant-quantity"
                            [value]="grantShares()"
                            (valueChange)="grantShares.set(numberOr($event, 0))"
                            type="number"
                            min="1000"
                            step="1000"
                            class="h-9 font-mono text-xs tabular-nums"
                          />
                        </div>
                      </div>
    
                      <div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
                        <div class="space-y-1.5">
                          <label class="text-foreground text-xs font-medium">Equity Class</label>
                          <div
                            class="border-border bg-muted/30 flex h-9 items-center justify-between rounded-md border px-3 text-xs"
                          >
                            <span class="font-medium">{{ grantClass() }}</span>
                            <span ui-badge variant="outline" class="text-xs">EIP-2024</span>
                          </div>
                        </div>
    
                        <div class="space-y-1.5">
                          <label class="text-foreground text-xs font-medium">Exercise Price (409A FMV)</label>
                          <div
                            class="border-border bg-muted/30 flex h-9 items-center justify-between rounded-md border px-3 font-mono text-xs tabular-nums"
                          >
                            <span>&#36;{{ currentSharePrice.toFixed(2) }} / share</span>
                            <span class="text-muted-foreground text-xs">Fair Market</span>
                          </div>
                        </div>
                      </div>
    
                      <div class="space-y-1.5">
                        <label class="text-foreground text-xs font-medium">Standard Vesting Schedule</label>
                        <ui-input
                          [value]="grantVesting()"
                          (valueChange)="grantVesting.set($event)"
                          class="bg-muted/40 h-9 text-xs"
                        />
                        <p class="text-muted-foreground text-xs">
                          25% vests at 12-month cliff; remaining 75% vests monthly in 36 equal installments.
                        </p>
                      </div>
    
                      <div class="flex items-center justify-end gap-2 pt-3">
                        <button ui-button type="button" variant="outline" size="sm" (click)="isGrantDialogOpen.set(false)">
                          Cancel
                        </button>
                        <button ui-button type="submit" size="sm" class="gap-1.5">
                          <lucide-icon [img]="Check" class="size-4" />
                          Draft &amp; Issue Grant
                        </button>
                      </div>
                    </form>
                  }
                </div>
              </div>
            </div>
          }
    
          <!-- 4 Capital Structure Overview Cards -->
          <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
            <!-- Total Fully Diluted Shares Card -->
            <div ui-card class="border-border relative overflow-hidden shadow-xs">
              <div ui-card-header class="flex flex-row items-center justify-between pb-2">
                <h3 ui-card-title class="text-muted-foreground text-xs font-medium">Total Fully Diluted Shares</h3>
                <div class="bg-primary/10 text-primary rounded-md p-1.5">
                  <lucide-icon [img]="Layers" class="size-4" />
                </div>
              </div>
              <div ui-card-content class="space-y-1">
                <div class="text-foreground text-2xl font-bold tracking-tight tabular-nums">10,000,000</div>
                <div class="text-muted-foreground flex flex-wrap items-center justify-between gap-x-2 text-xs">
                  <span>Authorized: 15,000,000</span>
                  <span class="text-success font-mono">66.7% Issued</span>
                </div>
              </div>
            </div>
    
            <!-- Founder Common Stock Card -->
            <div ui-card class="border-border relative overflow-hidden shadow-xs">
              <div ui-card-header class="flex flex-row items-center justify-between pb-2">
                <h3 ui-card-title class="text-muted-foreground text-xs font-medium">Founder Common Stock</h3>
                <div class="bg-success/10 text-success rounded-md p-1.5">
                  <lucide-icon [img]="Users" class="size-4" />
                </div>
              </div>
              <div ui-card-content class="space-y-1">
                <div class="flex items-baseline gap-2">
                  <span class="text-foreground text-2xl font-bold tracking-tight tabular-nums">6,000,000</span>
                  <span class="text-success font-mono text-xs font-semibold">60.0%</span>
                </div>
                <div class="text-muted-foreground flex flex-wrap items-center justify-between gap-x-2 text-xs">
                  <span>2 Co-Founders</span>
                  <span class="font-mono tabular-nums">$27,000,000.00</span>
                </div>
              </div>
            </div>
    
            <!-- Series Seed Preferred Card -->
            <div ui-card class="border-border relative overflow-hidden shadow-xs">
              <div ui-card-header class="flex flex-row items-center justify-between pb-2">
                <h3 ui-card-title class="text-muted-foreground text-xs font-medium">Series Seed Preferred</h3>
                <div class="bg-chart-2/10 text-chart-2 rounded-md p-1.5">
                  <lucide-icon [img]="TrendingUp" class="size-4" />
                </div>
              </div>
              <div ui-card-content class="space-y-1">
                <div class="flex items-baseline gap-2">
                  <span class="text-foreground text-2xl font-bold tracking-tight tabular-nums">2,500,000</span>
                  <span class="text-chart-2 font-mono text-xs font-semibold">25.0%</span>
                </div>
                <div class="text-muted-foreground flex flex-wrap items-center justify-between gap-x-2 text-xs">
                  <span>2 Seed Investors · 1.0x Pref</span>
                  <span class="font-mono tabular-nums">$11,250,000.00</span>
                </div>
              </div>
            </div>
    
            <!-- Unallocated Option Pool Card -->
            <div ui-card class="border-border relative overflow-hidden shadow-xs">
              <div ui-card-header class="flex flex-row items-center justify-between pb-2">
                <h3 ui-card-title class="text-muted-foreground text-xs font-medium">Unallocated Option Pool</h3>
                <div class="bg-info/10 text-info rounded-md p-1.5">
                  <lucide-icon [img]="Coins" class="size-4" />
                </div>
              </div>
              <div ui-card-content class="space-y-1">
                <div class="flex items-baseline gap-2">
                  <span class="text-foreground text-2xl font-bold tracking-tight tabular-nums">1,500,000</span>
                  <span class="text-info font-mono text-xs font-semibold">15.0%</span>
                </div>
                <div class="text-muted-foreground flex flex-wrap items-center justify-between gap-x-2 text-xs">
                  <span>2024 EIP Pool</span>
                  <span class="font-mono tabular-nums">$6,750,000.00</span>
                </div>
              </div>
            </div>
          </div>
    
          <!-- Company Valuation Hero & Visual Stacked Ownership Bar Card -->
          <div ui-card class="border-border overflow-hidden shadow-xs">
            <div ui-card-header class="pb-3">
              <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
                <div>
                  <p ui-card-description class="text-muted-foreground text-xs font-semibold tracking-wider uppercase">
                    Current Post-Money Valuation
                  </p>
                  <div class="mt-1 flex flex-wrap items-baseline gap-3">
                    <span class="text-foreground text-3xl font-bold tracking-tight tabular-nums sm:text-4xl">
                      $45,000,000.00
                    </span>
                    <div
                      class="border-success/20 bg-success/10 text-success text-success inline-flex items-center gap-1 rounded-full border px-2.5 py-0.5 text-xs font-semibold tabular-nums"
                    >
                      <lucide-icon [img]="TrendingUp" class="size-3.5" />
                      Seed Round Closed
                      <span class="text-muted-foreground ml-0.5 font-normal">($4.50/share)</span>
                    </div>
                  </div>
                </div>
    
                <div class="text-muted-foreground flex items-center gap-3 text-xs">
                  <div class="flex items-center gap-1.5">
                    <span class="bg-success size-2 rounded-full"></span>
                    <span>Cap Table Reconciled</span>
                  </div>
                  <span class="font-mono">10,000,000 Total Units</span>
                </div>
              </div>
            </div>
    
            <div ui-card-content class="space-y-4 pt-1">
              <!-- Multi-segment Stacked Ownership Bar -->
              <div class="space-y-2">
                <div class="text-muted-foreground flex flex-wrap items-center justify-between gap-x-2 text-xs">
                  <span class="text-foreground font-medium">Fully Diluted Equity Distribution</span>
                  <span class="tabular-nums">100.0% Allocation</span>
                </div>
    
                <div class="bg-muted/60 border-border/50 flex h-4 w-full gap-0.5 overflow-hidden rounded-full border p-0.5">
                  <!-- Founders 60% Emerald -->
                  <div
                    style="width: 60%"
                    class="bg-success h-full rounded-l-full transition-opacity duration-300 hover:opacity-90"
                    title="Founders (Common Stock): 60.0% · 6,000,000 Shares"
                  ></div>
                  <!-- Seed Investors 25% -->
                  <div
                    style="width: 25%"
                    class="bg-chart-2 h-full transition-opacity duration-300 hover:opacity-90"
                    title="Seed Investors (Preferred): 25.0% · 2,500,000 Shares"
                  ></div>
                  <!-- Option Pool 15% Blue -->
                  <div
                    style="width: 15%"
                    class="bg-info h-full rounded-r-full transition-opacity duration-300 hover:opacity-90"
                    title="Unallocated Option Pool: 15.0% · 1,500,000 Shares"
                  ></div>
                </div>
    
                <!-- Stacked Bar Legend -->
                <div class="grid grid-cols-1 gap-3 pt-2 sm:grid-cols-3">
                  <div
                    class="border-border/60 bg-muted/20 flex flex-wrap items-center justify-between gap-x-2 rounded-md border p-2.5 text-xs"
                  >
                    <div class="flex items-center gap-2">
                      <span class="bg-success size-2.5 shrink-0 rounded-full shadow-xs"></span>
                      <div>
                        <div class="text-foreground font-medium">Founders (Common)</div>
                        <div class="text-muted-foreground font-mono tabular-nums">6,000,000 shares</div>
                      </div>
                    </div>
                    <div class="text-right">
                      <div class="text-foreground font-bold tabular-nums">60.0%</div>
                      <div class="text-muted-foreground font-mono tabular-nums">$27,000,000.00</div>
                    </div>
                  </div>
    
                  <div
                    class="border-border/60 bg-muted/20 flex flex-wrap items-center justify-between gap-x-2 rounded-md border p-2.5 text-xs"
                  >
                    <div class="flex items-center gap-2">
                      <span class="bg-chart-2 size-2.5 shrink-0 rounded-full shadow-xs"></span>
                      <div>
                        <div class="text-foreground font-medium">Seed Investors (Preferred)</div>
                        <div class="text-muted-foreground font-mono tabular-nums">2,500,000 shares</div>
                      </div>
                    </div>
                    <div class="text-right">
                      <div class="text-foreground font-bold tabular-nums">25.0%</div>
                      <div class="text-muted-foreground font-mono tabular-nums">$11,250,000.00</div>
                    </div>
                  </div>
    
                  <div
                    class="border-border/60 bg-muted/20 flex flex-wrap items-center justify-between gap-x-2 rounded-md border p-2.5 text-xs"
                  >
                    <div class="flex items-center gap-2">
                      <span class="bg-info size-2.5 shrink-0 rounded-full shadow-xs"></span>
                      <div>
                        <div class="text-foreground font-medium">Employee Option Pool</div>
                        <div class="text-muted-foreground font-mono tabular-nums">1,500,000 shares</div>
                      </div>
                    </div>
                    <div class="text-right">
                      <div class="text-foreground font-bold tabular-nums">15.0%</div>
                      <div class="text-muted-foreground font-mono tabular-nums">$6,750,000.00</div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
    
          <!-- Shareholder Breakdown Table Card -->
          <div ui-card class="border-border shadow-xs">
            <div ui-card-header class="border-border/60 border-b pb-4">
              <div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
                <div>
                  <h2 ui-card-title class="text-foreground text-base font-semibold">Shareholder &amp; Equity Register</h2>
                  <p ui-card-description class="text-muted-foreground mt-0.5 text-xs">
                    Detailed registry of individual grants, share classes, fully diluted ownership, and vesting timelines.
                  </p>
                </div>
    
                <div class="flex flex-col gap-2 sm:flex-row sm:items-center">
                  <!-- Search -->
                  <div class="relative w-full sm:w-56">
                    <lucide-icon
                      [img]="Search"
                      class="text-muted-foreground absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2"
                    />
                    <ui-input
                      [value]="searchQuery()"
                      (valueChange)="searchQuery.set($event)"
                      placeholder="Search shareholder or role..."
                      class="h-8 pl-8 text-xs"
                    />
                  </div>
    
                  <!-- Share Class Filter Buttons -->
                  <div class="border-border bg-muted/40 flex items-center rounded-lg border p-0.5">
                    <button type="button" [class]="filterButtonClass('All')" (click)="selectedClassFilter.set('All')">
                      All (5)
                    </button>
                    <button
                      type="button"
                      [class]="filterButtonClass('Common Stock')"
                      (click)="selectedClassFilter.set('Common Stock')"
                    >
                      Common (2)
                    </button>
                    <button
                      type="button"
                      [class]="filterButtonClass('Series Seed Preferred')"
                      (click)="selectedClassFilter.set('Series Seed Preferred')"
                    >
                      Preferred (2)
                    </button>
                    <button
                      type="button"
                      [class]="filterButtonClass('ISO Option Pool')"
                      (click)="selectedClassFilter.set('ISO Option Pool')"
                    >
                      Pool (1)
                    </button>
                  </div>
                </div>
              </div>
            </div>
    
            <div ui-card-content class="p-0">
              <div class="overflow-x-auto">
                <table ui-table>
                  <thead ui-table-header>
                    <tr ui-table-row class="hover:bg-transparent">
                      <th ui-table-head class="w-[260px] pl-6 text-xs font-medium">Shareholder / Entity</th>
                      <th ui-table-head class="w-[180px] text-xs font-medium">Share Class</th>
                      <th ui-table-head class="text-right text-xs font-medium">Shares Owned</th>
                      <th ui-table-head class="text-right text-xs font-medium">Fully Diluted %</th>
                      <th ui-table-head class="text-right text-xs font-medium">Total Equity Value</th>
                      <th ui-table-head class="min-w-[240px] pr-6 text-xs font-medium">Vesting &amp; Status</th>
                    </tr>
                  </thead>
                  <tbody ui-table-body>
                    @for (sh of filteredShareholders(); track sh.id) {
                      <tr ui-table-row class="hover:bg-muted/30 transition-colors">
                        <!-- Shareholder Name & Role -->
                        <td ui-table-cell class="py-3.5 pl-6">
                          <div class="flex items-center gap-3">
                            <ui-avatar class="border-border/80 size-9 border shadow-xs">
                              <ui-avatar-fallback [class]="avatarFallbackClass(sh.shareClass)">
                                {{ sh.avatar }}
                              </ui-avatar-fallback>
                            </ui-avatar>
                            <div class="min-w-0">
                              <div class="text-foreground truncate text-xs font-semibold">{{ sh.name }}</div>
                              <div class="text-muted-foreground truncate text-xs">{{ sh.role }}</div>
                              <div class="text-muted-foreground/70 font-mono text-xs">{{ sh.grantDate }}</div>
                            </div>
                          </div>
                        </td>
    
                        <!-- Share Class Badge -->
                        <td ui-table-cell class="py-3.5">
                          @if (sh.shareClass === 'Common Stock') {
                            <span
                              ui-badge
                              variant="outline"
                              class="border-success/30 bg-success/10 text-success text-xs font-medium"
                            >
                              Common Stock
                            </span>
                          } @else if (sh.shareClass === 'Series Seed Preferred') {
                            <span
                              ui-badge
                              variant="outline"
                              class="border-chart-2/30 bg-chart-2/10 text-chart-2 text-xs font-medium"
                            >
                              Series Seed Preferred
                            </span>
                          } @else {
                            <span
                              ui-badge
                              variant="outline"
                              class="border-info/30 bg-info/10 text-info text-xs font-medium"
                            >
                              ISO Option Pool
                            </span>
                          }
                        </td>
    
                        <!-- Shares Owned -->
                        <td ui-table-cell class="py-3.5 text-right">
                          <div class="text-foreground font-mono text-sm font-bold tabular-nums">
                            {{ formatNumber(sh.shares) }}
                          </div>
                          <div class="text-muted-foreground text-xs">Shares</div>
                        </td>
    
                        <!-- Fully Diluted Ownership Percentage -->
                        <td ui-table-cell class="py-3.5 text-right">
                          <div class="text-foreground font-mono text-sm font-semibold tabular-nums">
                            {{ formatPercent(sh.ownershipPct) }}
                          </div>
                          <div class="text-muted-foreground text-xs">of 10,000,000</div>
                        </td>
    
                        <!-- Total Equity Value -->
                        <td ui-table-cell class="py-3.5 text-right">
                          <div class="text-foreground font-mono text-sm font-semibold tabular-nums">
                            {{ formatCurrency(sh.equityValue) }}
                          </div>
                          <div class="text-muted-foreground text-xs">@ $4.50/share</div>
                        </td>
    
                        <!-- Vesting Status Progress -->
                        <td ui-table-cell class="py-3.5 pr-6">
                          <div class="space-y-1.5">
                            <div class="flex flex-wrap items-center justify-between gap-x-2 text-xs">
                              <span class="text-foreground font-medium">{{ vestingLabel(sh) }}</span>
                              <span class="text-muted-foreground font-mono tabular-nums">
                                {{ sh.vestedMonths }}/{{ sh.totalMonths }} mo
                              </span>
                            </div>
    
                            <div class="bg-muted/80 relative h-2 w-full overflow-hidden rounded-full">
                              <div [class]="vestingBarClass(sh)" [style.width.%]="vestingPct(sh)"></div>
                            </div>
    
                            <div class="text-muted-foreground text-xs">{{ sh.vestingDetail }}</div>
                          </div>
                        </td>
                      </tr>
                    }
                  </tbody>
                </table>
              </div>
    
              <!-- Table Summary Bar -->
              <div
                class="border-border/60 bg-muted/20 flex flex-wrap items-center justify-between gap-4 border-t px-6 py-3 text-xs"
              >
                <div class="text-muted-foreground">
                  Showing <span class="text-foreground font-semibold">{{ filteredShareholders().length }}</span> of
                  <span class="text-foreground font-semibold">5</span> cap table stakeholders
                </div>
                <div class="flex flex-wrap items-center gap-6 font-mono">
                  <div>
                    <span class="text-muted-foreground">Total Shares: </span>
                    <span class="text-foreground font-bold tabular-nums">10,000,000</span>
                  </div>
                  <div>
                    <span class="text-muted-foreground">Total Ownership: </span>
                    <span class="text-foreground font-bold tabular-nums">100.0%</span>
                  </div>
                  <div>
                    <span class="text-muted-foreground">Total Valuation: </span>
                    <span class="text-foreground font-bold tabular-nums">$45,000,000.00</span>
                  </div>
                </div>
              </div>
            </div>
          </div>
    
          <!-- Round Modeling Simulator Card (Series A Dilution Simulator) -->
          <div ui-card class="border-border shadow-xs">
            <div ui-card-header class="pb-3">
              <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">
                    <div class="bg-primary/10 text-primary rounded-md p-1.5">
                      <lucide-icon [img]="Calculator" class="size-4" />
                    </div>
                    <h2 ui-card-title class="text-foreground text-base font-semibold">
                      Round Modeling Simulator (Series A Pro-Forma)
                    </h2>
                  </div>
                  <p ui-card-description class="text-muted-foreground text-xs">
                    Simulate Series A pre-money valuation and capital raised to preview diluted ownership percentages, new
                    share price, and stakeholder equity value accretion.
                  </p>
                </div>
    
                <!-- Preset Scenario Buttons -->
                <div class="flex flex-wrap items-center gap-1.5">
                  <button
                    ui-button
                    variant="outline"
                    size="sm"
                    class="h-7 text-xs font-medium"
                    (click)="setPresetScenario(30000000, 5000000)"
                  >
                    $5M @ $30M Pre
                  </button>
                  <button
                    ui-button
                    variant="outline"
                    size="sm"
                    class="h-7 text-xs font-medium"
                    (click)="setPresetScenario(50000000, 10000000)"
                  >
                    $10M @ $50M Pre
                  </button>
                  <button
                    ui-button
                    variant="outline"
                    size="sm"
                    class="h-7 text-xs font-medium"
                    (click)="setPresetScenario(60000000, 15000000)"
                  >
                    $15M @ $60M Pre
                  </button>
                </div>
              </div>
            </div>
    
            <div ui-card-content class="space-y-6">
              <!-- Simulator Inputs -->
              <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
                <!-- Pre-Money Valuation Input -->
                <div class="bg-muted/30 border-border/80 space-y-2 rounded-lg border p-4">
                  <div class="flex flex-wrap items-center justify-between gap-x-2 text-xs">
                    <label for="angular-sim-pre-money" class="text-foreground font-semibold">
                      Series A Pre-Money Valuation
                    </label>
                    <span class="text-muted-foreground font-mono tabular-nums">
                      &#36;{{ (simPreMoney() / 1000000).toFixed(1) }}M
                    </span>
                  </div>
                  <div class="flex items-center gap-3">
                    <ui-input
                      id="angular-sim-pre-money"
                      [value]="simPreMoney()"
                      (valueChange)="simPreMoney.set(numberOr($event, 0))"
                      type="number"
                      step="1000000"
                      min="10000000"
                      class="font-mono text-base font-bold tabular-nums"
                    />
                  </div>
                  <p class="text-muted-foreground text-xs">
                    Implies a share price of
                    <strong class="text-foreground font-mono tabular-nums">
                      &#36;{{ simNewSharePrice().toFixed(2) }} / share</strong
                    >
                    (+{{ stepUpPct().toFixed(1) }}% step-up).
                  </p>
                </div>
    
                <!-- New Investment Raised Input -->
                <div class="bg-muted/30 border-border/80 space-y-2 rounded-lg border p-4">
                  <div class="flex flex-wrap items-center justify-between gap-x-2 text-xs">
                    <label for="angular-sim-investment" class="text-foreground font-semibold"
                      >Series A Capital Raised</label
                    >
                    <span class="text-muted-foreground font-mono tabular-nums">
                      &#36;{{ (simInvestment() / 1000000).toFixed(1) }}M
                    </span>
                  </div>
                  <div class="flex items-center gap-3">
                    <ui-input
                      id="angular-sim-investment"
                      [value]="simInvestment()"
                      (valueChange)="simInvestment.set(numberOr($event, 0))"
                      type="number"
                      step="1000000"
                      min="1000000"
                      class="font-mono text-base font-bold tabular-nums"
                    />
                  </div>
                  <p class="text-muted-foreground text-xs">
                    Issues
                    <strong class="text-foreground font-mono tabular-nums">
                      {{ formatNumber(simNewSharesIssued()) }} new shares</strong
                    >
                    to Series A lead syndicate.
                  </p>
                </div>
              </div>
    
              <!-- Calculated Pro-Forma Summary Metrics -->
              <div class="grid grid-cols-2 gap-3 sm:grid-cols-4">
                <div class="border-border/60 bg-muted/20 space-y-1 rounded-md border p-3">
                  <div class="text-muted-foreground text-xs">Post-Money Valuation</div>
                  <div class="text-foreground font-mono text-lg font-bold tabular-nums">
                    {{ formatCurrency(simPostMoney()) }}
                  </div>
                  <div class="text-muted-foreground text-xs">Pre-Money + Investment</div>
                </div>
    
                <div class="border-border/60 bg-muted/20 space-y-1 rounded-md border p-3">
                  <div class="text-muted-foreground text-xs">Series A Share Price</div>
                  <div class="text-foreground font-mono text-lg font-bold tabular-nums">
                    &#36;{{ simNewSharePrice().toFixed(2) }}
                  </div>
                  <div class="text-success text-xs">
                    vs &#36;{{ currentSharePrice.toFixed(2) }} Seed (+{{ stepUpPct().toFixed(1) }}%)
                  </div>
                </div>
    
                <div class="border-border/60 bg-muted/20 space-y-1 rounded-md border p-3">
                  <div class="text-muted-foreground text-xs">Total Diluted Shares</div>
                  <div class="text-foreground font-mono text-lg font-bold tabular-nums">
                    {{ formatNumber(simTotalPostShares()) }}
                  </div>
                  <div class="text-muted-foreground text-xs">+{{ formatNumber(simNewSharesIssued()) }} New Shares</div>
                </div>
    
                <div class="border-border/60 bg-muted/20 space-y-1 rounded-md border p-3">
                  <div class="text-muted-foreground text-xs">Investor Dilution Rate</div>
                  <div class="text-foreground text-warning text-warning font-mono text-lg font-bold tabular-nums">
                    {{ formatPercent(simNewInvestorPct()) }}
                  </div>
                  <div class="text-muted-foreground text-xs">Effective Round Dilution</div>
                </div>
              </div>
    
              <!-- Post-Round Dilution & Valuation Comparison Table -->
              <div class="space-y-3">
                <div class="flex flex-wrap items-center justify-between gap-x-2 text-xs">
                  <span class="text-foreground font-semibold">Post-Series A Ownership Comparison</span>
                  <span class="text-muted-foreground">Pro-Forma Stakeholder Impact</span>
                </div>
    
                <!-- Visual Comparison Bar -->
                <div class="space-y-1.5">
                  <div
                    class="bg-muted/60 border-border/50 flex h-4 w-full gap-0.5 overflow-hidden rounded-full border p-0.5"
                  >
                    <div
                      [style.width.%]="simFounderPct()"
                      class="bg-success h-full rounded-l-full transition-[width] duration-300"
                      [title]="'Founders: ' + simFounderPct().toFixed(1) + '%'"
                    ></div>
                    <div
                      [style.width.%]="simSeedPct()"
                      class="bg-chart-2 h-full transition-[width] duration-300"
                      [title]="'Seed Investors: ' + simSeedPct().toFixed(1) + '%'"
                    ></div>
                    <div
                      [style.width.%]="simOptionPct()"
                      class="bg-info h-full transition-[width] duration-300"
                      [title]="'Option Pool: ' + simOptionPct().toFixed(1) + '%'"
                    ></div>
                    <div
                      [style.width.%]="simNewInvestorPct()"
                      class="bg-warning h-full rounded-r-full transition-[width] duration-300"
                      [title]="'Series A Investors: ' + simNewInvestorPct().toFixed(1) + '%'"
                    ></div>
                  </div>
                </div>
    
                <!-- Stakeholder Comparative Grid -->
                <div class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4">
                  <!-- Founders -->
                  <div class="border-border/80 bg-card space-y-2 rounded-lg border p-3 text-xs shadow-xs">
                    <div class="flex flex-wrap items-center justify-between gap-x-2">
                      <div class="flex items-center gap-1.5">
                        <span class="bg-success size-2 rounded-full"></span>
                        <span class="text-foreground font-semibold">Founders</span>
                      </div>
                      <span ui-badge variant="outline" class="border-success/30 text-success font-mono text-xs">
                        {{ formatPercent(simFounderPct()) }}
                      </span>
                    </div>
                    <div class="text-muted-foreground flex flex-wrap items-center justify-between gap-x-2">
                      <span>Dilution:</span>
                      <span class="text-destructive text-destructive font-mono tabular-nums">
                        60.0% &rarr; {{ formatPercent(simFounderPct()) }} (-{{ (60 - simFounderPct()).toFixed(1) }}%)
                      </span>
                    </div>
                    <div class="text-muted-foreground flex flex-wrap items-center justify-between gap-x-2">
                      <span>Equity Value:</span>
                      <span class="text-foreground font-mono font-semibold tabular-nums">
                        {{ formatCurrency(simFounderValue()) }}</span
                      >
                    </div>
                    <div class="text-success flex flex-wrap items-center justify-between gap-x-2 text-xs">
                      <span>Value Accretion:</span>
                      <span class="font-mono font-semibold tabular-nums">
                        +{{ formatCurrency(simFounderValue() - 27000000) }}</span
                      >
                    </div>
                  </div>
    
                  <!-- Seed Investors -->
                  <div class="border-border/80 bg-card space-y-2 rounded-lg border p-3 text-xs shadow-xs">
                    <div class="flex flex-wrap items-center justify-between gap-x-2">
                      <div class="flex items-center gap-1.5">
                        <span class="bg-chart-2 size-2 rounded-full"></span>
                        <span class="text-foreground font-semibold">Seed Investors</span>
                      </div>
                      <span ui-badge variant="outline" class="border-chart-2/30 text-chart-2 font-mono text-xs">
                        {{ formatPercent(simSeedPct()) }}
                      </span>
                    </div>
                    <div class="text-muted-foreground flex flex-wrap items-center justify-between gap-x-2">
                      <span>Dilution:</span>
                      <span class="text-destructive text-destructive font-mono tabular-nums">
                        25.0% &rarr; {{ formatPercent(simSeedPct()) }} (-{{ (25 - simSeedPct()).toFixed(1) }}%)
                      </span>
                    </div>
                    <div class="text-muted-foreground flex flex-wrap items-center justify-between gap-x-2">
                      <span>Equity Value:</span>
                      <span class="text-foreground font-mono font-semibold tabular-nums">
                        {{ formatCurrency(simSeedValue()) }}</span
                      >
                    </div>
                    <div class="text-success flex flex-wrap items-center justify-between gap-x-2 text-xs">
                      <span>Value Accretion:</span>
                      <span class="font-mono font-semibold tabular-nums">
                        +{{ formatCurrency(simSeedValue() - 11250000) }}</span
                      >
                    </div>
                  </div>
    
                  <!-- Option Pool -->
                  <div class="border-border/80 bg-card space-y-2 rounded-lg border p-3 text-xs shadow-xs">
                    <div class="flex flex-wrap items-center justify-between gap-x-2">
                      <div class="flex items-center gap-1.5">
                        <span class="bg-info size-2 rounded-full"></span>
                        <span class="text-foreground font-semibold">Option Pool</span>
                      </div>
                      <span ui-badge variant="outline" class="border-info/30 text-info font-mono text-xs">
                        {{ formatPercent(simOptionPct()) }}
                      </span>
                    </div>
                    <div class="text-muted-foreground flex flex-wrap items-center justify-between gap-x-2">
                      <span>Dilution:</span>
                      <span class="text-destructive text-destructive font-mono tabular-nums">
                        15.0% &rarr; {{ formatPercent(simOptionPct()) }} (-{{ (15 - simOptionPct()).toFixed(1) }}%)
                      </span>
                    </div>
                    <div class="text-muted-foreground flex flex-wrap items-center justify-between gap-x-2">
                      <span>Equity Value:</span>
                      <span class="text-foreground font-mono font-semibold tabular-nums">
                        {{ formatCurrency(simOptionValue()) }}</span
                      >
                    </div>
                    <div class="text-success flex flex-wrap items-center justify-between gap-x-2 text-xs">
                      <span>Value Accretion:</span>
                      <span class="font-mono font-semibold tabular-nums">
                        +{{ formatCurrency(simOptionValue() - 6750000) }}</span
                      >
                    </div>
                  </div>
    
                  <!-- New Series A Investors -->
                  <div class="border-border/80 bg-card space-y-2 rounded-lg border p-3 text-xs shadow-xs">
                    <div class="flex flex-wrap items-center justify-between gap-x-2">
                      <div class="flex items-center gap-1.5">
                        <span class="bg-warning size-2 rounded-full"></span>
                        <span class="text-foreground font-semibold">Series A Syndicate</span>
                      </div>
                      <span ui-badge variant="outline" class="border-warning/30 text-warning font-mono text-xs">
                        New Investor
                      </span>
                    </div>
                    <div class="text-muted-foreground flex flex-wrap items-center justify-between gap-x-2">
                      <span>New Ownership:</span>
                      <span class="text-warning text-warning font-mono font-bold tabular-nums">
                        {{ formatPercent(simNewInvestorPct()) }}
                      </span>
                    </div>
                    <div class="text-muted-foreground flex flex-wrap items-center justify-between gap-x-2">
                      <span>Shares Purchased:</span>
                      <span class="text-foreground font-mono font-semibold tabular-nums">
                        {{ formatNumber(simNewSharesIssued()) }}</span
                      >
                    </div>
                    <div class="text-muted-foreground flex flex-wrap items-center justify-between gap-x-2 text-xs">
                      <span>Capital Invested:</span>
                      <span class="text-foreground font-mono font-semibold tabular-nums">
                        {{ formatCurrency(simInvestment()) }}</span
                      >
                    </div>
                  </div>
                </div>
              </div>
    
              <!-- Insight Banner Callout -->
              <div class="border-border/60 bg-muted/40 flex items-start gap-3 rounded-lg border p-3.5 text-xs">
                <lucide-icon [img]="Info" class="text-primary mt-0.5 size-4 shrink-0" />
                <div class="text-muted-foreground space-y-1">
                  <span class="text-foreground font-semibold">Round Dynamics Takeaway: </span>
                  Raising
                  <span class="text-foreground font-mono font-medium">{{ formatCurrency(simInvestment()) }}</span> at a
                  <span class="text-foreground font-mono font-medium">{{ formatCurrency(simPreMoney()) }}</span> pre-money
                  valuation generates a
                  <span class="text-foreground font-mono font-medium">&#36;{{ simNewSharePrice().toFixed(2) }}</span> share
                  price. Although existing founders experience
                  <span class="text-foreground font-mono font-medium">{{ (60 - simFounderPct()).toFixed(1) }}%</span>
                  dilution, their net equity value increases by
                  <span class="text-success font-mono font-bold">+{{ formatCurrency(simFounderValue() - 27000000) }}</span>
                  (+{{ founderAccretionPct().toFixed(1) }}%) due to the valuation step-up.
                </div>
              </div>
            </div>
          </div>
        </div>
      `,
    })
    export class UiCapTableOwnershipSummaryComponent {
      protected readonly Award = Award
      protected readonly Building2 = Building2
      protected readonly Calculator = Calculator
      protected readonly Check = Check
      protected readonly Coins = Coins
      protected readonly FileSpreadsheet = FileSpreadsheet
      protected readonly Info = Info
      protected readonly Layers = Layers
      protected readonly Plus = Plus
      protected readonly RefreshCw = RefreshCw
      protected readonly Search = Search
      protected readonly ShieldCheck = ShieldCheck
      protected readonly TrendingUp = TrendingUp
      protected readonly Users = Users
      protected readonly X = X
    
      // Current Cap Table Static Baseline
      protected readonly currentSharePrice = 4.5
      protected readonly currentTotalShares = 10000000
      protected readonly currentValuation = 45000000
    
      @Input() set initialInvestment(v: number | undefined) {
        if (v !== undefined) this.simInvestment.set(v)
      }
      @Input() set initialPreMoney(v: number | undefined) {
        if (v !== undefined) this.simPreMoney.set(v)
      }
      @Input('class') className?: string
    
      readonly shareholders = signal<Shareholder[]>(INITIAL_SHAREHOLDERS.map((s) => ({ ...s })))
    
      // Filter and Search State
      readonly searchQuery = signal('')
      readonly selectedClassFilter = signal<CapTableClassFilter>('All')
    
      readonly filteredShareholders = computed(() => {
        const filter = this.selectedClassFilter()
        const q = this.searchQuery().trim().toLowerCase()
        return this.shareholders().filter((sh) => {
          if (filter !== 'All' && sh.shareClass !== filter) return false
          if (q) {
            return (
              sh.name.toLowerCase().includes(q) ||
              sh.role.toLowerCase().includes(q) ||
              sh.shareClass.toLowerCase().includes(q)
            )
          }
          return true
        })
      })
    
      // Export feedback state
      readonly isExporting = signal(false)
      readonly exportSuccess = signal(false)
      private exportTimer: ReturnType<typeof setTimeout> | undefined
      private exportResetTimer: ReturnType<typeof setTimeout> | undefined
    
      // Issue Grant Modal State
      readonly isGrantDialogOpen = signal(false)
      readonly grantSuccessMessage = signal(false)
      readonly grantName = signal('')
      readonly grantRole = signal('')
      readonly grantShares = signal(50000)
      readonly grantClass = signal<GrantEquityClass>('ISO Options')
      readonly grantVesting = signal('4-Year Vesting · 1-Year Cliff (25%), then Monthly')
      private grantTimer: ReturnType<typeof setTimeout> | undefined
    
      // Round Modeling Simulator State
      readonly simPreMoney = signal(50000000)
      readonly simInvestment = signal(10000000)
    
      // Dynamic Round Modeling Calculations
      readonly simPostMoney = computed(() => this.simPreMoney() + this.simInvestment())
      readonly simNewSharePrice = computed(() => {
        if (this.currentTotalShares <= 0) return 0
        return this.simPreMoney() / this.currentTotalShares
      })
      readonly simNewSharesIssued = computed(() => {
        if (this.simNewSharePrice() <= 0) return 0
        return Math.round(this.simInvestment() / this.simNewSharePrice())
      })
      readonly simTotalPostShares = computed(() => this.currentTotalShares + this.simNewSharesIssued())
    
      private readonly simFounderShares = 6000000
      private readonly simSeedShares = 2500000
      private readonly simOptionShares = 1500000
    
      readonly simFounderPct = computed(() => (this.simFounderShares / this.simTotalPostShares()) * 100)
      readonly simSeedPct = computed(() => (this.simSeedShares / this.simTotalPostShares()) * 100)
      readonly simOptionPct = computed(() => (this.simOptionShares / this.simTotalPostShares()) * 100)
      readonly simNewInvestorPct = computed(() => (this.simNewSharesIssued() / this.simTotalPostShares()) * 100)
    
      readonly simFounderValue = computed(() => (this.simFounderPct() / 100) * this.simPostMoney())
      readonly simSeedValue = computed(() => (this.simSeedPct() / 100) * this.simPostMoney())
      readonly simOptionValue = computed(() => (this.simOptionPct() / 100) * this.simPostMoney())
    
      readonly stepUpPct = computed(
        () => ((this.simNewSharePrice() - this.currentSharePrice) / this.currentSharePrice) * 100,
      )
      readonly founderAccretionPct = computed(() => ((this.simFounderValue() - 27000000) / 27000000) * 100)
    
      readonly exportButtonLabel = computed(() =>
        this.exportSuccess() ? 'Cap Table Exported!' : this.isExporting() ? 'Exporting...' : 'Export Cap Table (Excel)',
      )
    
      get rootClass(): string {
        return cn('mx-auto w-full max-w-6xl space-y-6', this.className)
      }
    
      filterButtonClass(value: CapTableClassFilter): string {
        return cn(
          'rounded-md px-2.5 py-1 text-xs font-medium transition-colors',
          this.selectedClassFilter() === value
            ? 'bg-background text-foreground shadow-xs'
            : 'text-muted-foreground hover:text-foreground',
        )
      }
    
      avatarFallbackClass(shareClass: CapTableShareClass): string {
        return cn(
          'text-xs font-bold',
          shareClass === 'Common Stock'
            ? 'bg-success/10 text-success'
            : shareClass === 'Series Seed Preferred'
              ? 'bg-chart-2/10 text-chart-2'
              : 'bg-info/10 text-info',
        )
      }
    
      vestingPct(sh: Shareholder): number {
        if (sh.isVestedComplete) return 100
        return (sh.vestedMonths / sh.totalMonths) * 100
      }
    
      vestingLabel(sh: Shareholder): string {
        if (sh.isVestedComplete) return '100% Vested'
        if (sh.vestedMonths > 0) return `${Math.round((sh.vestedMonths / sh.totalMonths) * 100)}% Vested`
        return 'Unallocated'
      }
    
      vestingBarClass(sh: Shareholder): string {
        return cn(
          'h-full transition-[width,background-color] duration-300',
          sh.isVestedComplete ? 'bg-primary' : sh.vestedMonths > 0 ? 'bg-success' : 'bg-info/40',
        )
      }
    
      numberOr(raw: string, fallback: number): number {
        const n = Number(raw)
        return Number.isFinite(n) ? n : fallback
      }
    
      formatCurrency(val: number): string {
        return val.toLocaleString('en-US', { style: 'currency', currency: 'USD' })
      }
    
      formatNumber(val: number): string {
        return val.toLocaleString('en-US')
      }
    
      formatPercent(val: number, decimals = 1): string {
        return `${val.toFixed(decimals)}%`
      }
    
      handleExportCapTable(): void {
        this.isExporting.set(true)
        clearTimeout(this.exportTimer)
        clearTimeout(this.exportResetTimer)
        this.exportTimer = setTimeout(() => {
          this.isExporting.set(false)
          this.exportSuccess.set(true)
          this.exportResetTimer = setTimeout(() => this.exportSuccess.set(false), 2500)
        }, 1000)
      }
    
      handleCreateGrant(event?: Event): void {
        event?.preventDefault()
        if (!this.grantName().trim() || this.grantShares() <= 0) return
        this.grantSuccessMessage.set(true)
        clearTimeout(this.grantTimer)
        this.grantTimer = setTimeout(() => {
          this.grantSuccessMessage.set(false)
          this.isGrantDialogOpen.set(false)
          this.grantName.set('')
          this.grantRole.set('')
          this.grantShares.set(50000)
        }, 1500)
      }
    
      setPresetScenario(preMoney: number, investment: number): void {
        this.simPreMoney.set(preMoney)
        this.simInvestment.set(investment)
      }
    }
    
  • src/app/components/blocks/cap-table-ownership-summary/index.ts0.2 kB

Raw manifest:https://uipkge.dev/r/angular/cap-table-ownership-summary.json