/** * — Interactive Sankey diagram showing revenue flow. * * Visualizes $29.99 example: Printer cost -> Creator share -> Community share. * Draggable split sliders, animated flow lines, key metrics. */ class FolkRevenueSankey extends HTMLElement { private shadow: ShadowRoot; private totalPrice = 29.99; private providerPct = 50; private creatorPct = 35; private communityPct = 15; private dragging: "creator" | "community" | null = null; constructor() { super(); this.shadow = this.attachShadow({ mode: "open" }); } connectedCallback() { if (this.hasAttribute("price")) { this.totalPrice = parseFloat(this.getAttribute("price") || "29.99"); } this.render(); } private render() { const providerAmt = (this.totalPrice * this.providerPct / 100).toFixed(2); const creatorAmt = (this.totalPrice * this.creatorPct / 100).toFixed(2); const communityAmt = (this.totalPrice * this.communityPct / 100).toFixed(2); this.shadow.innerHTML = `
Revenue Flow
How $${this.totalPrice.toFixed(2)} flows from customer to community
Customer $${this.totalPrice.toFixed(2)} Print Provider ${this.providerPct}% / $${providerAmt} Design Creator ${this.creatorPct}% / $${creatorAmt} Community ${this.communityPct}% / $${communityAmt} $0 platform fee
${this.providerPct}% $${providerAmt}
${this.creatorPct}% $${creatorAmt}
${this.communityPct}% $${communityAmt}
Provider ${this.providerPct}%
Creator ${this.creatorPct}%
Adjust sliders to explore different revenue splits
$0
Platform Fee
100%
To Community
${this.creatorPct + this.communityPct}%
Creator + Commons
Print Provider (production + shipping) Design Creator Community Treasury
`; this.bindEvents(); } private bindEvents() { const providerSlider = this.shadow.getElementById("provider-slider") as HTMLInputElement; const creatorSlider = this.shadow.getElementById("creator-slider") as HTMLInputElement; providerSlider?.addEventListener("input", () => { this.providerPct = parseInt(providerSlider.value, 10); this.rebalance("provider"); this.render(); }); creatorSlider?.addEventListener("input", () => { this.creatorPct = parseInt(creatorSlider.value, 10); this.rebalance("creator"); this.render(); }); } private rebalance(changed: "provider" | "creator") { if (changed === "provider") { // Adjust creator and community proportionally const remaining = 100 - this.providerPct; const ratio = this.creatorPct / (this.creatorPct + this.communityPct) || 0.7; this.creatorPct = Math.round(remaining * ratio); this.communityPct = remaining - this.creatorPct; } else { // Adjust community from remaining this.communityPct = 100 - this.providerPct - this.creatorPct; } // Clamp this.communityPct = Math.max(0, this.communityPct); if (this.providerPct + this.creatorPct + this.communityPct !== 100) { this.communityPct = 100 - this.providerPct - this.creatorPct; } } } customElements.define("folk-revenue-sankey", FolkRevenueSankey);