Safe Note Calculator
blockfinanceY Combinator Post-Money SAFE note valuation, conversion share price, effective discount, and cap table equity dilution simulator with interactive parameter controls and Series A financing sensitivity matrix.
Installation
$pnpm dlx uipkge-ng@latest add safe-note-calculator$npx uipkge-ng@latest add safe-note-calculator$yarn dlx uipkge-ng@latest add safe-note-calculator$bunx uipkge-ng@latest add safe-note-calculatorManifest:
https://uipkge.dev/r/angular/safe-note-calculator.jsonInstalls to:src/app/components/blocks/safe-note-calculator/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.
PresetConfiginterface PresetConfig {
id: RoundPreset
label: string
investment: number
valuationCap: number
hasDiscount: boolean
discountRate: number
shares: number
seriesAPreMoney: number
description: string
}Files installed (2)
src/app/components/blocks/safe-note-calculator/safe-note-calculator.component.ts43.5 kB
// Y Combinator Post-Money SAFE note valuation, conversion share price, effective discount, // and cap table equity dilution simulator with interactive parameter controls and a Series A // financing sensitivity matrix. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, DestroyRef, Input, computed, inject, signal } from '@angular/core' import { BadgePercent, Check, CheckCircle2, Copy, DollarSign, FileSpreadsheet, Layers, LucideAngularModule, Percent, PieChart, Scale, ShieldCheck, TrendingUp, } 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, UiCardFooterComponent, UiCardHeaderComponent, UiCardTitleComponent, } from '@/ui/card/card.component' import { UiInputComponent } from '@/ui/input/input.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' import { UiSliderComponent } from '@/ui/slider/slider.component' import { UiSwitchComponent } from '@/ui/switch/switch.component' import { UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableFooterComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, } from '@/ui/table/table.component' import { UiTabsComponent, UiTabsListComponent, UiTabsTriggerComponent } from '@/ui/tabs/tabs.component' export type RoundPreset = 'seed-standard' | 'preseed-safe' | 'uncapped-discount' | 'bridge-round' export interface PresetConfig { id: RoundPreset label: string investment: number valuationCap: number hasDiscount: boolean discountRate: number shares: number seriesAPreMoney: number description: string } export const PRESETS: Record<RoundPreset, PresetConfig> = { 'seed-standard': { id: 'seed-standard', label: 'Standard Seed ($1M @ $15M)', investment: 1_000_000, valuationCap: 15_000_000, hasDiscount: true, discountRate: 20, shares: 10_000_000, seriesAPreMoney: 25_000_000, description: 'Standard YC Post-Money SAFE with $15M valuation cap and optional 20% discount clause.', }, 'preseed-safe': { id: 'preseed-safe', label: 'Pre-Seed ($500K @ $8M)', investment: 500_000, valuationCap: 8_000_000, hasDiscount: true, discountRate: 20, shares: 10_000_000, seriesAPreMoney: 18_000_000, description: 'Early-stage angel or accelerator round with $8M valuation cap.', }, 'uncapped-discount': { id: 'uncapped-discount', label: 'Uncapped (25% Disc)', investment: 750_000, valuationCap: 60_000_000, hasDiscount: true, discountRate: 25, shares: 10_000_000, seriesAPreMoney: 20_000_000, description: 'High valuation cap structure where the 25% discount governs share conversion price.', }, 'bridge-round': { id: 'bridge-round', label: 'Growth Bridge ($2M @ $25M)', investment: 2_000_000, valuationCap: 25_000_000, hasDiscount: true, discountRate: 15, shares: 10_000_000, seriesAPreMoney: 45_000_000, description: 'Late seed extension bridging into a larger institutional Series A equity round.', }, } const INVESTMENT_MARKS: Record<number, string> = { 100000: '$100K', 1000000: '$1M', 2500000: '$2.5M', 5000000: '$5M', } const VALUATION_CAP_MARKS: Record<number, string> = { 2000000: '$2M', 15000000: '$15M', 30000000: '$30M', 50000000: '$50M', } const DISCOUNT_RATE_MARKS: Record<number, string> = { 10: '10%', 15: '15%', 20: '20%', 25: '25%', 30: '30%', } const SERIES_A_MARKS: Record<number, string> = { 5000000: '$5M', 25000000: '$25M', 50000000: '$50M', 80000000: '$80M', } const SCENARIO_VALUATIONS = [10_000_000, 15_000_000, 20_000_000, 25_000_000, 40_000_000, 60_000_000] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-safe-note-calculator, [ui-safe-note-calculator]', 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, UiCardFooterComponent, UiCardHeaderComponent, UiCardTitleComponent, UiInputComponent, UiSeparatorComponent, UiSliderComponent, UiSwitchComponent, UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableFooterComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, UiTabsComponent, UiTabsListComponent, UiTabsTriggerComponent, ], template: ` <div data-slot="safe-note-calculator" [class]="rootClass"> <!-- Header --> <div class="flex flex-col items-center gap-4 text-center"> <span ui-badge wrap variant="outline" class="gap-1.5 px-3 py-1 text-xs font-medium tracking-wider uppercase"> <lucide-icon [img]="Scale" class="text-primary size-3.5" /> YC Post-Money SAFE v1.1 · Valuation Cap + Discount </span> <div class="space-y-2"> <h2 class="text-foreground text-2xl font-bold tracking-tight sm:text-3xl lg:text-4xl"> Y Combinator Post-Money SAFE Calculator </h2> <p class="text-muted-foreground mx-auto max-w-2xl text-sm sm:text-base"> Model founder dilution, investor ownership percentages, and conversion price per share. </p> </div> <!-- Preset Tabs --> <div class="mt-2 flex w-full justify-center"> <ui-tabs [value]="selectedPreset()" (valueChange)="handlePresetChange($event)" class="w-full sm:w-auto"> <ui-tabs-list variant="segmented" class="grid w-full grid-cols-2 sm:flex sm:w-auto"> <button ui-tabs-trigger value="seed-standard" class="gap-1.5"> <lucide-icon [img]="ShieldCheck" class="size-3.5" /> Standard Seed </button> <button ui-tabs-trigger value="preseed-safe" class="gap-1.5"> <lucide-icon [img]="TrendingUp" class="size-3.5" /> Pre-Seed SAFE </button> <button ui-tabs-trigger value="uncapped-discount" class="gap-1.5"> <lucide-icon [img]="BadgePercent" class="size-3.5" /> Uncapped Disc </button> <button ui-tabs-trigger value="bridge-round" class="gap-1.5"> <lucide-icon [img]="Layers" class="size-3.5" /> Growth Bridge </button> </ui-tabs-list> </ui-tabs> </div> </div> <!-- 2-Column Calculator Layout --> <div class="grid grid-cols-1 gap-8 lg:grid-cols-12 lg:items-start"> <!-- Left Input Card --> <div class="space-y-6 lg:col-span-7"> <div ui-card> <div ui-card-header> <h3 ui-card-title class="flex items-center gap-2"> <lucide-icon [img]="DollarSign" class="text-primary size-5" /> SAFE Terms & Round Inputs </h3> <p ui-card-description> Configure investment size, valuation cap, optional discount rate, capitalization, and Series A targets. </p> </div> <div ui-card-content class="space-y-7"> <!-- 1. Investment Amount --> <div class="space-y-3.5"> <div class="flex items-center justify-between gap-4"> <div> <span class="text-foreground text-sm font-medium">Investment Amount</span> <p class="text-muted-foreground text-xs">Total cash invested into the SAFE.</p> </div> <div class="w-36 sm:w-44"> <ui-input id="safe-investment-input" type="number" size="small" [min]="25000" [max]="10000000" [step]="25000" prefix="$" [value]="investmentAmount()" (valueChange)="investmentAmount.set(+$event || 0)" /> </div> </div> <div class="pb-5"> <ui-slider [value]="[investmentAmount()]" (valueChange)="investmentAmount.set($event[0] ?? 0)" [min]="100000" [max]="5000000" [step]="25000" [marks]="investmentMarks" [tooltip]="investTooltip" /> </div> <div class="flex flex-wrap items-center gap-1.5 pt-1"> <span class="text-muted-foreground text-xs">Quick select:</span> @for (amt of quickInvestments; track amt) { <button ui-button variant="outline" size="sm" class="h-6 px-2 text-xs" (click)="investmentAmount.set(amt)" > {{ quickInvestLabel(amt) }} </button> } </div> </div> <ui-separator /> <!-- 2. Post-Money Valuation Cap --> <div class="space-y-3.5"> <div class="flex items-center justify-between gap-4"> <div> <span class="text-foreground text-sm font-medium">Post-Money Valuation Cap</span> <p class="text-muted-foreground text-xs">Maximum valuation ceiling for conversion.</p> </div> <div class="w-36 sm:w-44"> <ui-input id="safe-cap-input" type="number" size="small" [min]="500000" [max]="100000000" [step]="500000" prefix="$" [value]="valuationCap()" (valueChange)="valuationCap.set(+$event || 0)" /> </div> </div> <div class="pb-5"> <ui-slider [value]="[valuationCap()]" (valueChange)="valuationCap.set($event[0] ?? 0)" [min]="2000000" [max]="50000000" [step]="500000" [marks]="valuationCapMarks" [tooltip]="currencyTooltip" /> </div> <div class="flex flex-wrap items-center gap-1.5 pt-1"> <span class="text-muted-foreground text-xs">Cap benchmarks:</span> @for (cap of capBenchmarks; track cap) { <button ui-button variant="outline" size="sm" class="h-6 px-2 text-xs" (click)="valuationCap.set(cap)" > \${{ cap / 1000000 }}M </button> } </div> </div> <ui-separator /> <!-- 3. Discount Rate Input & Toggle --> <div class="space-y-3.5"> <div class="flex items-center justify-between gap-4"> <div class="space-y-1"> <div class="flex items-center gap-2"> <span class="text-foreground text-sm font-medium">Discount Rate Clause</span> @if (hasDiscount()) { <span ui-badge wrap variant="outline" class="border-success/20 bg-success/10 text-success text-xs font-normal" > Active </span> } @else { <span ui-badge wrap variant="outline" class="text-muted-foreground text-xs font-normal"> Cap Only </span> } </div> <p class="text-muted-foreground text-xs"> Percentage discount on Series A price if cap is not exceeded. </p> </div> <div class="flex flex-wrap items-center gap-3"> <ui-switch [checked]="hasDiscount()" (checkedChange)="hasDiscount.set($event)" aria-label="Toggle discount clause" /> @if (hasDiscount()) { <div class="w-24 sm:w-28"> <ui-input id="safe-discount-input" type="number" size="small" [min]="0" [max]="50" [step]="1" suffix="%" [value]="discountRate()" (valueChange)="discountRate.set(+$event || 0)" /> </div> } </div> </div> @if (hasDiscount()) { <div class="space-y-3 pt-1"> <div class="pb-5"> <ui-slider [value]="[discountRate()]" (valueChange)="discountRate.set($event[0] ?? 0)" [min]="5" [max]="35" [step]="1" [marks]="discountRateMarks" [tooltip]="discountTooltip" /> </div> <div class="flex flex-wrap items-center gap-1.5 pt-1"> <span class="text-muted-foreground text-xs">Standard rates:</span> @for (r of standardRates; track r) { <button ui-button [variant]="discountRate() === r ? 'default' : 'outline'" size="sm" class="h-6 px-2 text-xs" (click)="discountRate.set(r)" > {{ r }}% </button> } </div> </div> } </div> <ui-separator /> <!-- 4. Company Existing Capitalization --> <div class="space-y-3.5"> <div class="flex items-center justify-between gap-4"> <div> <span class="text-foreground text-sm font-medium">Company Existing Capitalization</span> <p class="text-muted-foreground text-xs">Total pre-conversion issued shares and option pool.</p> </div> <div class="w-36 sm:w-44"> <ui-input id="safe-shares-input" type="number" size="small" [min]="1000000" [max]="100000000" [step]="500000" suffix="Shares" [value]="companyCapitalization()" (valueChange)="companyCapitalization.set(+$event || 0)" /> </div> </div> <div class="flex flex-wrap items-center gap-1.5 pt-1"> <span class="text-muted-foreground text-xs">Common share counts:</span> @for (count of shareCounts; track count) { <button ui-button [variant]="companyCapitalization() === count ? 'default' : 'outline'" size="sm" class="h-6 px-2 text-xs" (click)="companyCapitalization.set(count)" > {{ count / 1000000 }}M Shares </button> } </div> </div> <ui-separator /> <!-- 5. Series A Pre-Money Target --> <div class="space-y-3.5"> <div class="flex items-center justify-between gap-4"> <div> <span class="text-foreground text-sm font-medium">Series A Pre-Money Target</span> <p class="text-muted-foreground text-xs">Target qualified financing equity valuation.</p> </div> <div class="w-36 sm:w-44"> <ui-input id="safe-series-a-input" type="number" size="small" [min]="1000000" [max]="200000000" [step]="1000000" prefix="$" [value]="seriesAPreMoney()" (valueChange)="seriesAPreMoney.set(+$event || 0)" /> </div> </div> <div class="pb-5"> <ui-slider [value]="[seriesAPreMoney()]" (valueChange)="seriesAPreMoney.set($event[0] ?? 0)" [min]="5000000" [max]="80000000" [step]="1000000" [marks]="seriesAMarks" [tooltip]="currencyTooltip" /> </div> <div class="flex flex-wrap items-center gap-1.5 pt-1"> <span class="text-muted-foreground text-xs">Series A benchmarks:</span> @for (target of seriesABenchmarks; track target) { <button ui-button variant="outline" size="sm" class="h-6 px-2 text-xs" (click)="seriesAPreMoney.set(target)" > \${{ target / 1000000 }}M </button> } </div> </div> </div> </div> </div> <!-- Right Results Card (Sticky on desktop) --> <div class="lg:sticky lg:top-8 lg:col-span-5"> <div ui-card class="border-border bg-card shadow-xs"> <div ui-card-header class="pb-4"> <div class="flex items-center justify-between"> <span class="text-muted-foreground text-xs font-semibold tracking-wider uppercase"> Investor Ownership Stake </span> <span ui-badge variant="secondary" [class]="governingBadgeClass"> {{ calculations().governingMechanism === 'cap' ? 'Cap Governs' : 'Discount Governs' }} </span> </div> <div class="mt-4"> <div class="flex items-baseline gap-2"> <span class="text-foreground text-3xl font-bold tracking-tight tabular-nums sm:text-4xl"> {{ fmtPercent(calculations().safeOwnershipPct) }} </span> <span class="text-muted-foreground text-sm font-medium">Post-SAFE Ownership</span> </div> <p class="text-muted-foreground mt-1 text-xs"> SAFE investor receives <span class="text-foreground font-semibold tabular-nums"> {{ fmtNumber(calculations().safeShares) }} </span> shares at conversion. </p> </div> </div> <div ui-card-content class="space-y-5"> <ui-separator /> <!-- Key Economics Metrics Grid --> <div class="grid grid-cols-1 gap-3 sm:grid-cols-2"> <div class="bg-muted/40 rounded-lg border p-3"> <span class="text-muted-foreground block text-xs">SAFE Conversion Price</span> <span class="text-foreground text-base font-bold tabular-nums"> {{ fmtCurrency(calculations().conversionPrice) }} </span> <span class="text-muted-foreground block text-xs tabular-nums"> vs Series A {{ fmtCurrency(calculations().seriesAPricePerShare) }} </span> </div> <div class="bg-muted/40 rounded-lg border p-3"> <span class="text-muted-foreground block text-xs">Converted Shares Issued</span> <span class="text-foreground text-base font-bold tabular-nums"> {{ fmtNumber(calculations().safeShares) }} </span> <span class="text-muted-foreground block text-xs">Equity units</span> </div> <div class="bg-muted/40 rounded-lg border p-3"> <span class="text-muted-foreground block text-xs">Effective Discount</span> <span class="text-success text-success text-base font-bold tabular-nums"> {{ fmtPercent(calculations().effectiveDiscountPct) }} </span> <span class="text-muted-foreground block text-xs">Savings vs Series A</span> </div> <div class="bg-muted/40 rounded-lg border p-3"> <span class="text-muted-foreground block text-xs">Implied Series A Value</span> <span class="text-foreground text-base font-bold tabular-nums"> {{ fmtCurrencyWhole(calculations().impliedSeriesAValue) }} </span> <span class="text-success text-success block text-xs tabular-nums"> +{{ fmtCurrencyWhole(calculations().valueGain) }} ({{ calculations().paperRoiMultiple.toFixed(2) }}x) </span> </div> </div> <ui-separator /> <!-- Visual Equity Distribution Bar --> <div class="space-y-2.5"> <div class="flex items-center justify-between text-xs font-medium"> <span class="text-foreground">Founders vs. SAFE Investor</span> <span class="text-muted-foreground tabular-nums"> {{ fmtPercent(calculations().founderOwnershipPct) }} / {{ fmtPercent(calculations().safeOwnershipPct) }} </span> </div> <div class="bg-muted relative flex h-3 w-full overflow-hidden rounded-full shadow-inner"> <div class="bg-primary h-full transition-[width] duration-300" [style.width]="founderBarWidth"></div> <div class="bg-success h-full transition-[width] duration-300" [style.width]="safeBarWidth"></div> </div> <div class="text-muted-foreground flex flex-col gap-1.5 text-xs sm:flex-row sm:items-center sm:justify-between" > <div class="flex items-center gap-1.5"> <span class="bg-primary size-2 shrink-0 rounded-full"></span> <span> Founders: {{ fmtPercent(calculations().founderOwnershipPct) }} ({{ fmtNumber(calculations().founderShares) }} sh) </span> </div> <div class="flex items-center gap-1.5"> <span class="bg-success size-2 shrink-0 rounded-full"></span> <span> SAFE: {{ fmtPercent(calculations().safeOwnershipPct) }} ({{ fmtNumber(calculations().safeShares) }} sh) </span> </div> </div> </div> <ui-separator /> <!-- Post-Conversion Dilution Breakdown Table --> <div class="space-y-2"> <span class="text-foreground text-xs font-semibold tracking-wider uppercase"> Post-Conversion Dilution Breakdown </span> <div class="overflow-hidden rounded-lg border"> <div class="overflow-x-auto"> <table ui-table density="compact"> <thead ui-table-header> <tr ui-table-row> <th ui-table-head class="text-xs">Stakeholder</th> <th ui-table-head class="text-right text-xs">Ownership</th> <th ui-table-head class="text-right text-xs">Shares</th> <th ui-table-head class="text-right text-xs">Implied Value</th> </tr> </thead> <tbody ui-table-body> <tr ui-table-row> <td ui-table-cell class="text-xs font-medium">Founders & Existing</td> <td ui-table-cell class="text-right text-xs font-medium tabular-nums"> {{ fmtPercent(calculations().founderOwnershipPct) }} </td> <td ui-table-cell class="text-right text-xs tabular-nums"> {{ fmtNumber(calculations().founderShares) }} </td> <td ui-table-cell class="text-right text-xs tabular-nums"> {{ fmtCurrencyWhole(calculations().founderShares * calculations().seriesAPricePerShare) }} </td> </tr> <tr ui-table-row> <td ui-table-cell class="text-success text-xs font-medium">SAFE Seed Investors</td> <td ui-table-cell class="text-success text-success text-right text-xs font-bold tabular-nums"> {{ fmtPercent(calculations().safeOwnershipPct) }} </td> <td ui-table-cell class="text-right text-xs font-semibold tabular-nums"> {{ fmtNumber(calculations().safeShares) }} </td> <td ui-table-cell class="text-right text-xs font-semibold tabular-nums"> {{ fmtCurrencyWhole(calculations().impliedSeriesAValue) }} </td> </tr> </tbody> <tfoot ui-table-footer> <tr ui-table-row> <td ui-table-cell class="text-xs font-bold">Total Post-SAFE</td> <td ui-table-cell class="text-right text-xs font-bold tabular-nums">100.00%</td> <td ui-table-cell class="text-right text-xs font-bold tabular-nums"> {{ fmtNumber(calculations().totalShares) }} </td> <td ui-table-cell class="text-right text-xs font-bold tabular-nums"> {{ fmtCurrencyWhole(seriesAPreMoney()) }} </td> </tr> </tfoot> </table> </div> </div> </div> <ui-separator /> <!-- Key SAFE Takeaways Checklist --> <ul class="text-muted-foreground space-y-2 text-xs"> <li class="flex items-center gap-2"> <lucide-icon [img]="CheckCircle2" class="text-primary size-3.5 shrink-0" /> <span>Post-money cap fixes ownership before Series A round</span> </li> <li class="flex items-center gap-2"> <lucide-icon [img]="CheckCircle2" class="text-primary size-3.5 shrink-0" /> <span>Converts to Preferred Stock with investor liquidation preference</span> </li> <li class="flex items-center gap-2"> <lucide-icon [img]="CheckCircle2" class="text-primary size-3.5 shrink-0" /> <span>No debt maturity dates, no interest accrual compounding</span> </li> </ul> </div> <div ui-card-footer class="flex flex-col gap-2.5 pt-2"> <button ui-button class="w-full gap-2 font-semibold shadow-xs" size="lg" (click)="copySummary()"> @if (copied()) { <lucide-icon [img]="Check" class="text-success size-4" /> } @else { <lucide-icon [img]="Copy" class="size-4" /> } {{ copied() ? 'Copied Summary to Clipboard!' : 'Copy Valuation Breakdown' }} </button> <button ui-button variant="outline" class="w-full gap-2" size="default"> <lucide-icon [img]="FileSpreadsheet" class="size-4" /> Export Cap Table Summary </button> </div> </div> </div> </div> <!-- Bottom Section: Series A Valuation Sensitivity Table --> <div ui-card> <div ui-card-header class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"> <div> <h3 ui-card-title class="flex items-center gap-2"> <lucide-icon [img]="TrendingUp" class="text-primary size-5" /> Series A Qualified Financing Sensitivity Matrix </h3> <p ui-card-description> Simulate how SAFE investor ownership percentage, share price, and conversion mechanism respond across different next-round valuations. </p> </div> </div> <div ui-card-content> <div class="overflow-x-auto"> <table ui-table density="cozy"> <thead ui-table-header> <tr ui-table-row> <th ui-table-head>Series A Valuation</th> <th ui-table-head class="text-right">Series A Price</th> <th ui-table-head class="text-right">SAFE Conversion Price</th> <th ui-table-head>Governing Term</th> <th ui-table-head class="text-right">Shares Issued</th> <th ui-table-head class="text-right">SAFE Ownership</th> <th ui-table-head class="text-right">Effective Savings</th> <th ui-table-head class="text-right">Implied Stake Value</th> </tr> </thead> <tbody ui-table-body> @for (row of scenarioResults(); track row.valuation) { <tr ui-table-row [class]="row.isCurrentTarget ? 'bg-primary/5 font-medium' : ''"> <td ui-table-cell class="font-medium"> <div class="flex items-center gap-2"> <span class="tabular-nums">{{ fmtCurrencyWhole(row.valuation) }}</span> @if (row.isCurrentTarget) { <span ui-badge wrap variant="default" class="h-5 px-1.5 text-xs"> Current Target </span> } </div> </td> <td ui-table-cell class="text-right tabular-nums">{{ fmtCurrency(row.seriesAPrice) }}</td> <td ui-table-cell class="text-foreground text-right font-semibold tabular-nums"> {{ fmtCurrency(row.convPrice) }} </td> <td ui-table-cell> <span ui-badge variant="outline" [class]="scenarioBadgeClass(row.mechanism)"> {{ row.mechanism }} </span> </td> <td ui-table-cell class="text-right tabular-nums">{{ fmtNumber(row.issuedShares) }}</td> <td ui-table-cell class="text-foreground text-right font-bold tabular-nums"> {{ fmtPercent(row.ownershipPct) }} </td> <td ui-table-cell class="text-success text-success text-right font-medium tabular-nums"> {{ fmtPercent(row.effectiveDiscount) }} </td> <td ui-table-cell class="text-right font-semibold tabular-nums"> {{ fmtCurrencyWhole(row.impliedValue) }} </td> </tr> } </tbody> </table> </div> </div> </div> <!-- Post-Money SAFE Legal & Governance Guide --> <div class="grid grid-cols-1 gap-4 sm:grid-cols-3"> <div ui-card class="p-4"> <div class="flex items-start gap-3"> <div class="bg-primary/10 text-primary mt-0.5 rounded-md p-2"> <lucide-icon [img]="ShieldCheck" class="size-4" /> </div> <div class="space-y-1"> <h4 class="text-foreground text-sm font-semibold">Post-Money Cap Protection</h4> <p class="text-muted-foreground text-xs leading-relaxed"> Acts as an ownership percentage guarantee for seed investors prior to the priced round, regardless of subsequent SAFE issuances. </p> </div> </div> </div> <div ui-card class="p-4"> <div class="flex items-start gap-3"> <div class="bg-success/10 text-success mt-0.5 rounded-md p-2"> <lucide-icon [img]="Percent" class="size-4" /> </div> <div class="space-y-1"> <h4 class="text-foreground text-sm font-semibold">Discount Rate Safety Net</h4> <p class="text-muted-foreground text-xs leading-relaxed"> Protects investors if Series A prices below the valuation cap, guaranteeing a 15%–25% discount off the institutional share price. </p> </div> </div> </div> <div ui-card class="p-4"> <div class="flex items-start gap-3"> <div class="bg-warning/10 text-warning mt-0.5 rounded-md p-2"> <lucide-icon [img]="PieChart" class="size-4" /> </div> <div class="space-y-1"> <h4 class="text-foreground text-sm font-semibold">Founder Dilution Transparency</h4> <p class="text-muted-foreground text-xs leading-relaxed"> YC v1.1 post-money SAFEs dilute only founders and common shareholders, eliminating pre-money SAFE circular dilution surprises. </p> </div> </div> </div> </div> </div> `, }) export class UiSafeNoteCalculatorComponent { protected readonly BadgePercent = BadgePercent protected readonly Check = Check protected readonly CheckCircle2 = CheckCircle2 protected readonly Copy = Copy protected readonly DollarSign = DollarSign protected readonly FileSpreadsheet = FileSpreadsheet protected readonly Layers = Layers protected readonly Percent = Percent protected readonly PieChart = PieChart protected readonly Scale = Scale protected readonly ShieldCheck = ShieldCheck protected readonly TrendingUp = TrendingUp protected readonly investmentMarks = INVESTMENT_MARKS protected readonly valuationCapMarks = VALUATION_CAP_MARKS protected readonly discountRateMarks = DISCOUNT_RATE_MARKS protected readonly seriesAMarks = SERIES_A_MARKS protected readonly quickInvestments = [250000, 500000, 1000000, 1500000, 2000000, 3000000] protected readonly capBenchmarks = [5000000, 8000000, 12000000, 15000000, 20000000, 30000000] protected readonly standardRates = [10, 15, 20, 25, 30] protected readonly shareCounts = [5000000, 8000000, 10000000, 12000000, 15000000] protected readonly seriesABenchmarks = [15000000, 20000000, 25000000, 35000000, 50000000, 75000000] protected readonly investTooltip = (val: number): string => `$${val.toLocaleString('en-US')}` protected readonly currencyTooltip = (val: number): string => `$${val.toLocaleString('en-US')}` protected readonly discountTooltip = (val: number): string => `${val}% Discount` @Input('class') className?: string readonly selectedPreset = signal<RoundPreset>('seed-standard') readonly investmentAmount = signal(1_000_000) readonly valuationCap = signal(15_000_000) readonly hasDiscount = signal(true) readonly discountRate = signal(20) readonly companyCapitalization = signal(10_000_000) readonly seriesAPreMoney = signal(25_000_000) readonly copied = signal(false) private copyTimer: ReturnType<typeof setTimeout> | undefined constructor() { inject(DestroyRef).onDestroy(() => clearTimeout(this.copyTimer)) } handlePresetChange(value: string): void { const presetId = value as RoundPreset this.selectedPreset.set(presetId) const preset = PRESETS[presetId] if (preset) { this.investmentAmount.set(preset.investment) this.valuationCap.set(preset.valuationCap) this.hasDiscount.set(preset.hasDiscount) this.discountRate.set(preset.discountRate) this.companyCapitalization.set(preset.shares) this.seriesAPreMoney.set(preset.seriesAPreMoney) } } // Core SAFE conversion economics readonly calculations = computed(() => { const inv = Math.max(1, this.investmentAmount()) const cap = Math.max(1, this.valuationCap()) const shares = Math.max(1, this.companyCapitalization()) const seriesA = Math.max(1, this.seriesAPreMoney()) const disc = this.hasDiscount() ? Math.max(0, Math.min(99, this.discountRate())) : 0 // Series A unqualified price per share const seriesAPricePerShare = seriesA / shares // Cap conversion price per share const capPricePerShare = cap / shares // Discount conversion price per share const discountPricePerShare = this.hasDiscount() ? seriesAPricePerShare * (1 - disc / 100) : seriesAPricePerShare // In YC Post-Money SAFE, investor converts at the lower of Cap Price or Discount Price let conversionPrice = capPricePerShare let governingMechanism: 'cap' | 'discount' = 'cap' if (this.hasDiscount() && discountPricePerShare < capPricePerShare) { conversionPrice = discountPricePerShare governingMechanism = 'discount' } else { conversionPrice = capPricePerShare governingMechanism = 'cap' } // Converted shares issued const safeShares = conversionPrice > 0 ? Math.round(inv / conversionPrice) : 0 // Equity percentage calculation const safeOwnershipPct = Math.min(100, Math.max(0, (safeShares / shares) * 100)) const founderShares = Math.max(0, shares - safeShares) const founderOwnershipPct = Math.max(0, 100 - safeOwnershipPct) // Effective discount achieved vs Series A price const effectiveDiscountPct = seriesAPricePerShare > 0 ? Math.max(0, ((seriesAPricePerShare - conversionPrice) / seriesAPricePerShare) * 100) : 0 // Implied Value of SAFE stake at Series A price const impliedSeriesAValue = safeShares * seriesAPricePerShare const valueGain = Math.max(0, impliedSeriesAValue - inv) const paperRoiMultiple = inv > 0 ? impliedSeriesAValue / inv : 1 return { seriesAPricePerShare, capPricePerShare, discountPricePerShare, conversionPrice, governingMechanism, safeShares, founderShares, totalShares: shares, safeOwnershipPct, founderOwnershipPct, effectiveDiscountPct, impliedSeriesAValue, valueGain, paperRoiMultiple, } }) // Sensitivity scenarios across different Series A pre-money valuations readonly scenarioResults = computed(() => { const inv = Math.max(1, this.investmentAmount()) const cap = Math.max(1, this.valuationCap()) const shares = Math.max(1, this.companyCapitalization()) const disc = this.hasDiscount() ? Math.max(0, Math.min(99, this.discountRate())) : 0 return SCENARIO_VALUATIONS.map((scenarioValuation) => { const seriesAPrice = scenarioValuation / shares const capPrice = cap / shares const discountPrice = this.hasDiscount() ? seriesAPrice * (1 - disc / 100) : seriesAPrice let convPrice = capPrice let mechanism = 'Valuation Cap' if (this.hasDiscount() && discountPrice < capPrice) { convPrice = discountPrice mechanism = `Discount (${disc}%)` } const issuedShares = convPrice > 0 ? Math.round(inv / convPrice) : 0 const ownershipPct = (issuedShares / shares) * 100 const effectiveDiscount = seriesAPrice > 0 ? Math.max(0, ((seriesAPrice - convPrice) / seriesAPrice) * 100) : 0 const impliedValue = issuedShares * seriesAPrice const isCurrentTarget = scenarioValuation === this.seriesAPreMoney() return { valuation: scenarioValuation, seriesAPrice, convPrice, mechanism, issuedShares, ownershipPct, effectiveDiscount, impliedValue, isCurrentTarget, } }) }) get rootClass(): string { return cn('mx-auto w-full max-w-6xl space-y-8 p-4 sm:p-6 lg:p-8', this.className) } get governingBadgeClass(): string { return cn( this.calculations().governingMechanism === 'cap' ? 'bg-primary/10 text-primary border-primary/20 text-xs font-semibold' : 'border-success/20 bg-success/10 text-success text-xs font-semibold', ) } get founderBarWidth(): string { return `${this.calculations().founderOwnershipPct}%` } get safeBarWidth(): string { return `${this.calculations().safeOwnershipPct}%` } scenarioBadgeClass(mechanism: string): string { return cn( mechanism === 'Valuation Cap' ? 'border-primary/20 bg-primary/10 text-primary text-xs' : 'border-success/20 bg-success/10 text-success text-xs', ) } quickInvestLabel(amt: number): string { return amt >= 1000000 ? `$${(amt / 1000000).toFixed(amt % 1000000 === 0 ? 0 : 1)}M` : `$${amt / 1000}K` } fmtCurrency(val: number): string { return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2, }).format(val || 0) } fmtCurrencyWhole(val: number): string { return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 0, maximumFractionDigits: 0, }).format(val || 0) } fmtPercent(val: number, decimals = 2): string { return `${(val || 0).toFixed(decimals)}%` } fmtNumber(val: number): string { return new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(val || 0) } copySummary(): void { const c = this.calculations() const text = `YC Post-Money SAFE Conversion Summary: - Investment Amount: ${this.fmtCurrency(this.investmentAmount())} - Valuation Cap: ${this.fmtCurrency(this.valuationCap())} - Series A Target: ${this.fmtCurrency(this.seriesAPreMoney())} Pre-Money - Investor Ownership: ${this.fmtPercent(c.safeOwnershipPct)} (${this.fmtNumber(c.safeShares)} shares) - Conversion Price: ${this.fmtCurrency(c.conversionPrice)} / share (vs Series A ${this.fmtCurrency(c.seriesAPricePerShare)}) - Effective Discount: ${this.fmtPercent(c.effectiveDiscountPct)} - Governing Term: ${c.governingMechanism === 'cap' ? 'Valuation Cap' : 'Discount Rate'}` try { navigator.clipboard?.writeText(text) } catch { // Clipboard unavailable in some contexts; the copied state still confirms the action. } this.copied.set(true) clearTimeout(this.copyTimer) this.copyTimer = setTimeout(() => { this.copied.set(false) }, 2000) } }src/app/components/blocks/safe-note-calculator/index.ts0.1 kB
export { UiSafeNoteCalculatorComponent, PRESETS, type RoundPreset, type PresetConfig, } from './safe-note-calculator.component'
Raw manifest:https://uipkge.dev/r/angular/safe-note-calculator.json