Parent / Core
Divisions
Subsidiaries
Offerings
Services
Drag bubbles to pin • Shift+drag to unpin • Scroll to zoom
import React, { useMemo, useState } from "react";
import { Card } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";
import { Slider } from "@/components/ui/slider";
import { Download, Plus, Trash2, RefreshCcw } from "lucide-react";
/**
* Equity & Phantom Exit Calculator
*
* Designed for Brella (parent) + brand subsidiaries like Popsicle Air & Levi's Garage.
* - Adjust sale prices per company
* - Adjust brand equity splits (Parent vs external investors)
* - Adjust brand-level phantom (e.g., D‑Buck on Popsicle, Levi on Levi's Garage)
* - Adjust parent-level phantom (e.g., Viv, Piero, Jake across all exits)
* - Choose phantom payout model: CARVE-OUT (from equity pool) or ON-TOP (extra cost)
* - Final payouts: brand equity holders, brand phantom recipients, parent phantom recipients, and parent equity holders (Zac, David)
*
* Notes:
* - All percentages are in % (0–100). Internally converted to decimals for math.
* - Totals validate visually; out-of-range sums show warnings.
* - This is a single-file React component ready to embed in your site.
*/
// --------------------------- Types ---------------------------
type PersonKey = string;
type EquityHolder = {
id: PersonKey;
name: string;
percent: number; // % at this layer
};
type PhantomHolder = {
id: PersonKey;
name: string;
percent: number; // % phantom against sale price (brand) or against parent net (parent)
};
type Brand = {
id: string;
name: string;
salePrice: number; // dollars
equityHolders: EquityHolder[]; // at brand level (e.g., Parent vs Investor(s)) must sum to 100
phantomHolders: PhantomHolder[]; // brand-level phantom (e.g., D‑Buck, Levi)
};
// --------------------------- Helpers ---------------------------
const toMoney = (n: number) =>
n.toLocaleString(undefined, { style: "currency", currency: "USD", maximumFractionDigits: 0 });
const clampPct = (p: number) => Math.max(0, Math.min(100, p));
// --------------------------- Component ---------------------------
export default function EquityPhantomExitCalculator() {
// Phantom model toggle: carve-out vs on-top
const [brandPhantomCarveOut, setBrandPhantomCarveOut] = useState(true);
const [parentPhantomCarveOut, setParentPhantomCarveOut] = useState(true);
// Parent equity (Brella) internal owners
const [parentEquity, setParentEquity] = useState([
{ id: "zac", name: "Zac", percent: 90 },
{ id: "david", name: "David", percent: 10 },
]);
// Parent-level phantom (applies to all brand proceeds flowing to parent net)
const [parentPhantom, setParentPhantom] = useState([
{ id: "viv", name: "Viv", percent: 2 },
{ id: "piero", name: "Piero", percent: 2 },
{ id: "jake", name: "Jake", percent: 0.5 },
]);
// Brands (start with Popsicle & Levi's with defaults you discussed)
const [brands, setBrands] = useState([
{
id: "popsicle",
name: "Popsicle Air",
salePrice: 100_000_000,
equityHolders: [
{ id: "parent", name: "Parent (Brella)", percent: 100 },
// Add external investor rows here if needed
],
phantomHolders: [
{ id: "dbuck", name: "D‑Buck (Brand Phantom)", percent: 3 },
],
},
{
id: "levis",
name: "Levi's Garage",
salePrice: 100_000_000,
equityHolders: [
{ id: "parent", name: "Parent (Brella)", percent: 100 },
],
phantomHolders: [
{ id: "levi", name: "Levi (Brand Phantom)", percent: 3 },
],
},
]);
// --------------------------- Calculations ---------------------------
const brandResults = useMemo(() => {
return brands.map((brand) => {
const sale = brand.salePrice;
const brandPhantomPct = brand.phantomHolders.reduce((a, h) => a + (h.percent || 0), 0) / 100;
const phantomPayout = sale * brandPhantomPct; // if on-top, paid in addition; if carve-out, subtracted from equity pool
const equityPool = brandPhantomCarveOut ? sale - phantomPayout : sale;
// Equity payouts at brand level
const equityPayouts = brand.equityHolders.map((eh) => ({
id: eh.id,
name: eh.name,
amount: equityPool * (clampPct(eh.percent) / 100),
}));
// Phantom payouts at brand level
const phantomPayouts = brand.phantomHolders.map((ph) => ({
id: ph.id,
name: ph.name,
amount: sale * (clampPct(ph.percent) / 100),
}));
return {
brandId: brand.id,
brandName: brand.name,
sale,
equityPool,
phantomPayout,
equityPayouts,
phantomPayouts,
equitySum: equityPayouts.reduce((a, r) => a + r.amount, 0),
phantomSum: phantomPayouts.reduce((a, r) => a + r.amount, 0),
equityPercentTotal: brand.equityHolders.reduce((a, r) => a + clampPct(r.percent), 0),
phantomPercentTotal: brand.phantomHolders.reduce((a, r) => a + clampPct(r.percent), 0),
};
});
}, [brands, brandPhantomCarveOut]);
// Total cash flowing to Parent (Brella) from all brands before parent phantom
const totalToParentBeforeParentPhantom = useMemo(() => {
return brandResults.reduce((sum, br) => {
const row = br.equityPayouts.find((e) => e.id === "parent");
return sum + (row ? row.amount : 0);
}, 0);
}, [brandResults]);
// Parent phantom payouts
const parentPhantomPct = useMemo(
() => parentPhantom.reduce((a, h) => a + clampPct(h.percent), 0) / 100,
[parentPhantom]
);
const parentPhantomPayout = parentPhantomCarveOut
? totalToParentBeforeParentPhantom * parentPhantomPct
: 0; // if ON-TOP, we don't reduce the pool here (we could show separately if desired)
const parentEquityPoolAfterParentPhantom = parentPhantomCarveOut
? totalToParentBeforeParentPhantom - parentPhantomPayout
: totalToParentBeforeParentPhantom;
// Parent equity split (Zac / David)
const parentEquityPayouts = useMemo(() => {
return parentEquity.map((eh) => ({
id: eh.id,
name: eh.name,
amount: parentEquityPoolAfterParentPhantom * (clampPct(eh.percent) / 100),
}));
}, [parentEquity, parentEquityPoolAfterParentPhantom]);
const parentPhantomPayouts = useMemo(() => {
const base = parentPhantomCarveOut
? totalToParentBeforeParentPhantom
: totalToParentBeforeParentPhantom; // same base for display; carve-out toggles reduction path
return parentPhantom.map((ph) => ({
id: ph.id,
name: ph.name,
amount: base * (clampPct(ph.percent) / 100),
}));
}, [parentPhantom, parentPhantomCarveOut, totalToParentBeforeParentPhantom]);
// --------------------------- UI Helpers ---------------------------
const updateBrand = (id: string, updater: (b: Brand) => Brand) => {
setBrands((prev) => prev.map((b) => (b.id === id ? updater(b) : b)));
};
const addBrand = () => {
const idx = brands.length + 1;
setBrands((prev) => [
...prev,
{
id: `brand_${idx}`,
name: `New Company ${idx}`,
salePrice: 50_000_000,
equityHolders: [{ id: "parent", name: "Parent (Brella)", percent: 100 }],
phantomHolders: [],
},
]);
};
const removeBrand = (id: string) => setBrands((prev) => prev.filter((b) => b.id !== id));
const downloadCSV = () => {
const rows: string[] = [];
rows.push(
[
"Layer",
"Entity",
"Name",
"Amount",
].join(",")
);
brandResults.forEach((br) => {
br.equityPayouts.forEach((p) => {
rows.push(["Brand Equity", br.brandName, p.name, Math.round(p.amount).toString()].join(","));
});
br.phantomPayouts.forEach((p) => {
rows.push(["Brand Phantom", br.brandName, p.name, Math.round(p.amount).toString()].join(","));
});
});
parentEquityPayouts.forEach((p) => {
rows.push(["Parent Equity", "Brella", p.name, Math.round(p.amount).toString()].join(","));
});
parentPhantomPayouts.forEach((p) => {
rows.push(["Parent Phantom", "Brella", p.name, Math.round(p.amount).toString()].join(","));
});
const blob = new Blob([rows.join("\n")], { type: "text/csv;charset=utf-8;" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `brella_exit_calculator_${Date.now()}.csv`;
a.click();
URL.revokeObjectURL(url);
};
const resetDefaults = () => {
setBrandPhantomCarveOut(true);
setParentPhantomCarveOut(true);
setParentEquity([
{ id: "zac", name: "Zac", percent: 90 },
{ id: "david", name: "David", percent: 10 },
]);
setParentPhantom([
{ id: "viv", name: "Viv", percent: 2 },
{ id: "piero", name: "Piero", percent: 2 },
{ id: "jake", name: "Jake", percent: 0.5 },
]);
setBrands([
{
id: "popsicle",
name: "Popsicle Air",
salePrice: 100_000_000,
equityHolders: [{ id: "parent", name: "Parent (Brella)", percent: 100 }],
phantomHolders: [{ id: "dbuck", name: "D‑Buck (Brand Phantom)", percent: 3 }],
},
{
id: "levis",
name: "Levi's Garage",
salePrice: 100_000_000,
equityHolders: [{ id: "parent", name: "Parent (Brella)", percent: 100 }],
phantomHolders: [{ id: "levi", name: "Levi (Brand Phantom)", percent: 3 }],
},
]);
};
// --------------------------- Render ---------------------------
return (