max / makenotwork
9 files changed,
+2 insertions,
-348 deletions
| @@ -239,62 +239,6 @@ chargeback_rate_tier_subs = 0.001 # A12: 0.1% for recurring (lower than one- | |||
| 239 | 239 | # - cap_count > 0, cap_months > 0 | |
| 240 | 240 | ||
| 241 | 241 | ||
| 242 | - | # ─── Per-tier monthly fee allocation (Mode 1 widget on /pricing) ───────── | |
| 243 | - | # | |
| 244 | - | # Each row sums to the standard tier price. The widget renders standard-rate | |
| 245 | - | # numbers only; founder-rate creators pay 50% of standard with the subsidy | |
| 246 | - | # absorbed by reserves + earnback, so the allocation shape is the same but | |
| 247 | - | # the visible total scales down — a one-line note on the pricing page covers | |
| 248 | - | # that case. | |
| 249 | - | # | |
| 250 | - | # Model (per `site-docs/public/about/economics.md`): | |
| 251 | - | # - Stripe: 2.9% × price + $0.30 (Stripe's actual fee formula) | |
| 252 | - | # - Storage: at-rest + egress + virus-scan + backups + provider-price | |
| 253 | - | # cushion; ~5–15× the bare Hetzner at-rest math from | |
| 254 | - | # [creator_marginal] to absorb spikes | |
| 255 | - | # - Support: flat $5.00/creator — per-creator floor, doesn't scale | |
| 256 | - | # with tier (a Basic text creator and a $60 video creator | |
| 257 | - | # each get the same human availability) | |
| 258 | - | # - Engineering: flat $6.00/creator — engineering hours don't scale with | |
| 259 | - | # what tier someone bought; the same platform serves all | |
| 260 | - | # - Reserves: 12.5% of revenue (risk float for refunds/chargebacks/runway) | |
| 261 | - | # - Earn-back: residual; the surplus from higher tiers funds platform | |
| 262 | - | # development that benefits everyone | |
| 263 | - | # | |
| 264 | - | # Numbers below are pinned by `tier_prices::tests::cost_allocation_*`. | |
| 265 | - | [cost_allocation.basic] | |
| 266 | - | stripe = 0.76 | |
| 267 | - | storage = 0.40 | |
| 268 | - | support = 5.00 | |
| 269 | - | engineering = 6.00 | |
| 270 | - | reserves = 2.00 | |
| 271 | - | earnback = 1.84 | |
| 272 | - | ||
| 273 | - | [cost_allocation.small_files] | |
| 274 | - | stripe = 1.00 | |
| 275 | - | storage = 1.00 | |
| 276 | - | support = 5.00 | |
| 277 | - | engineering = 6.00 | |
| 278 | - | reserves = 3.00 | |
| 279 | - | earnback = 8.00 | |
| 280 | - | ||
| 281 | - | [cost_allocation.big_files] | |
| 282 | - | stripe = 1.34 | |
| 283 | - | storage = 3.00 | |
| 284 | - | support = 5.00 | |
| 285 | - | engineering = 6.00 | |
| 286 | - | reserves = 4.50 | |
| 287 | - | earnback = 16.16 | |
| 288 | - | ||
| 289 | - | [cost_allocation.everything] | |
| 290 | - | stripe = 2.04 | |
| 291 | - | storage = 6.00 | |
| 292 | - | support = 5.00 | |
| 293 | - | engineering = 6.00 | |
| 294 | - | reserves = 7.50 | |
| 295 | - | earnback = 33.46 | |
| 296 | - | ||
| 297 | - | ||
| 298 | 242 | # ─── Runway disclosure (transparency block on /docs/about/economics) ───── | |
| 299 | 243 | # | |
| 300 | 244 | # Operator-set. Refreshed quarterly at sprint close, alongside the |
| @@ -91,7 +91,6 @@ pub struct AppState { | |||
| 91 | 91 | pub email: EmailClient, | |
| 92 | 92 | pub docs: Arc<DocLoader>, | |
| 93 | 93 | pub tier_prices: tier_prices::TierPrices, | |
| 94 | - | pub cost_allocation: tier_prices::CostAllocation, | |
| 95 | 94 | pub runway_config: tier_prices::RunwayConfig, | |
| 96 | 95 | /// Public `/pricing` comparison model (competitor fee tables + Stripe | |
| 97 | 96 | /// fees), parsed from `assumptions.toml` at startup. |
| @@ -398,10 +398,6 @@ async fn main() { | |||
| 398 | 398 | email, | |
| 399 | 399 | docs, | |
| 400 | 400 | tier_prices: makenotwork::tier_prices::TierPrices::from_assumptions(&assumptions), | |
| 401 | - | cost_allocation: { | |
| 402 | - | let tp = makenotwork::tier_prices::TierPrices::from_assumptions(&assumptions); | |
| 403 | - | makenotwork::tier_prices::CostAllocation::from_assumptions(&assumptions, &tp) | |
| 404 | - | }, | |
| 405 | 401 | runway_config: makenotwork::tier_prices::RunwayConfig::from_assumptions(&assumptions), | |
| 406 | 402 | pricing_comparison: makenotwork::pricing_comparison::PricingComparison::load(&assumptions_path), | |
| 407 | 403 | scanner, |
| @@ -437,7 +437,6 @@ pub(super) async fn pricing_page( | |||
| 437 | 437 | PricingTemplate { | |
| 438 | 438 | csrf_token: get_csrf_token(&session).await, | |
| 439 | 439 | tier_prices: state.tier_prices.clone(), | |
| 440 | - | cost_allocation: state.cost_allocation.clone(), | |
| 441 | 440 | comparison, | |
| 442 | 441 | } | |
| 443 | 442 | } |
| @@ -828,7 +828,6 @@ pub struct DocIndexTemplate { | |||
| 828 | 828 | pub struct PricingTemplate { | |
| 829 | 829 | pub csrf_token: CsrfTokenOption, | |
| 830 | 830 | pub tier_prices: crate::tier_prices::TierPrices, | |
| 831 | - | pub cost_allocation: crate::tier_prices::CostAllocation, | |
| 832 | 831 | /// Server-computed initial comparison (default revenue + Basic tier). The | |
| 833 | 832 | /// page re-fetches this partial via HTMX as the inputs change. | |
| 834 | 833 | pub comparison: crate::pricing_comparison::ComparisonResult, |
| @@ -138,124 +138,6 @@ impl TierPrices { | |||
| 138 | 138 | } | |
| 139 | 139 | } | |
| 140 | 140 | ||
| 141 | - | /// One segment of the per-tier monthly fee bar. Sized in cents so the | |
| 142 | - | /// template can drop it straight into `flex: <cents>` for a stacked | |
| 143 | - | /// horizontal bar that scales proportionally without per-segment math. | |
| 144 | - | #[derive(Clone, Debug)] | |
| 145 | - | pub struct CostSegment { | |
| 146 | - | /// CSS modifier and toml key — `"stripe"`, `"storage"`, `"support"`, | |
| 147 | - | /// `"engineering"`, `"reserves"`, `"earnback"`. Drives `.cost-bar-seg-*`. | |
| 148 | - | pub kind: &'static str, | |
| 149 | - | /// Human label e.g. `"Stripe processing"`. | |
| 150 | - | pub label: &'static str, | |
| 151 | - | /// Pre-formatted dollar string e.g. `"$0.76"` for the segment + tooltip. | |
| 152 | - | pub amount: String, | |
| 153 | - | /// Amount in cents — the flex weight for the stacked bar. | |
| 154 | - | pub cents: i32, | |
| 155 | - | /// Pre-formatted hover text (rendered into `title="…"`). Kept on the | |
| 156 | - | /// Rust side so copy reviews land here and not in the template. | |
| 157 | - | pub tooltip: String, | |
| 158 | - | } | |
| 159 | - | ||
| 160 | - | /// One row of the cost-allocation widget — a single tier and its six | |
| 161 | - | /// fee segments. The template iterates `rows` and per row iterates | |
| 162 | - | /// `segments`, so the markup is fully uniform. | |
| 163 | - | #[derive(Clone, Debug)] | |
| 164 | - | pub struct CostAllocationRow { | |
| 165 | - | pub tier_key: &'static str, | |
| 166 | - | pub tier_label: &'static str, | |
| 167 | - | /// Monthly price in dollars (whole-dollar tiers today). | |
| 168 | - | pub tier_price: i32, | |
| 169 | - | pub segments: Vec<CostSegment>, | |
| 170 | - | /// Pre-built `aria-label` for the bar — names every segment + value | |
| 171 | - | /// so screen readers get the breakdown. | |
| 172 | - | pub aria_label: String, | |
| 173 | - | } | |
| 174 | - | ||
| 175 | - | /// All four tier rows, in canonical Basic → Everything order. | |
| 176 | - | #[derive(Clone, Debug, Default)] | |
| 177 | - | pub struct CostAllocation { | |
| 178 | - | pub rows: Vec<CostAllocationRow>, | |
| 179 | - | } | |
| 180 | - | ||
| 181 | - | impl CostAllocation { | |
| 182 | - | pub fn from_assumptions(a: &Assumptions, tp: &TierPrices) -> Self { | |
| 183 | - | let rows = vec![ | |
| 184 | - | row(a, "basic", "Basic", tp.basic_std), | |
| 185 | - | row(a, "small_files", "Small Files", tp.small_files_std), | |
| 186 | - | row(a, "big_files", "Big Files", tp.big_files_std), | |
| 187 | - | row(a, "everything", "Everything", tp.everything_std), | |
| 188 | - | ]; | |
| 189 | - | Self { rows } | |
| 190 | - | } | |
| 191 | - | } | |
| 192 | - | ||
| 193 | - | fn row(a: &Assumptions, key: &'static str, label: &'static str, price: i32) -> CostAllocationRow { | |
| 194 | - | let segments = vec![ | |
| 195 | - | seg(a, key, "stripe", "Stripe processing", | |
| 196 | - | "Stripe processing: {amount}. Stripe's fee on your monthly subscription."), | |
| 197 | - | seg(a, key, "storage", "Storage", | |
| 198 | - | "Storage: {amount}. At-rest storage, bandwidth, virus scanning, and \ | |
| 199 | - | backups at your tier's typical fill, sized to absorb spikes and \ | |
| 200 | - | provider price changes without a price increase to you."), | |
| 201 | - | seg(a, key, "support", "Human support time", | |
| 202 | - | "Human support time: {amount}. Identity recovery, billing disputes, \ | |
| 203 | - | moderation, abuse, and legal — the work that can't be automated. \ | |
| 204 | - | Per-creator floor; the same at every tier."), | |
| 205 | - | seg(a, key, "engineering", "Product engineering", | |
| 206 | - | "Product engineering: {amount}. Bug fixes and ongoing product work. \ | |
| 207 | - | Per-creator floor; the same at every tier."), | |
| 208 | - | seg(a, key, "reserves", "Reserves", | |
| 209 | - | "Reserves: {amount}. Held against a bad month — refunds, chargebacks, \ | |
| 210 | - | unexpected costs — so a single incident doesn't force a price change \ | |
| 211 | - | or shutdown."), | |
| 212 | - | seg(a, key, "earnback", "Earn-back (returned to creators)", | |
| 213 | - | "Earn-back: {amount}. The margin above cost, reinvested to build the \ | |
| 214 | - | platform out and lower prices as we grow, with the rest returned to \ | |
| 215 | - | creators as earn-back credit (launching by 2027-01-01). It's how a \ | |
| 216 | - | fair platform gets big and stays that way."), | |
| 217 | - | ]; | |
| 218 | - | let aria_label = build_aria_label(label, price, &segments); | |
| 219 | - | CostAllocationRow { | |
| 220 | - | tier_key: key, | |
| 221 | - | tier_label: label, | |
| 222 | - | tier_price: price, | |
| 223 | - | segments, | |
| 224 | - | aria_label, | |
| 225 | - | } | |
| 226 | - | } | |
| 227 | - | ||
| 228 | - | fn seg( | |
| 229 | - | a: &Assumptions, | |
| 230 | - | tier_key: &str, | |
| 231 | - | seg_kind: &'static str, | |
| 232 | - | seg_label: &'static str, | |
| 233 | - | tooltip_template: &str, | |
| 234 | - | ) -> CostSegment { | |
| 235 | - | let dollars = float_at(a, &format!("cost_allocation.{tier_key}.{seg_kind}")); | |
| 236 | - | let cents = (dollars * 100.0).round() as i32; | |
| 237 | - | let amount = format!("${dollars:.2}"); | |
| 238 | - | let tooltip = tooltip_template.replace("{amount}", &amount); | |
| 239 | - | CostSegment { | |
| 240 | - | kind: seg_kind, | |
| 241 | - | label: seg_label, | |
| 242 | - | amount, | |
| 243 | - | cents, | |
| 244 | - | tooltip, | |
| 245 | - | } | |
| 246 | - | } | |
| 247 | - | ||
| 248 | - | fn build_aria_label(tier_label: &str, price: i32, segments: &[CostSegment]) -> String { | |
| 249 | - | let mut s = format!("{tier_label} tier ${price}/mo allocation: "); | |
| 250 | - | for (i, seg) in segments.iter().enumerate() { | |
| 251 | - | if i > 0 { s.push_str(", "); } | |
| 252 | - | s.push_str(seg.label); | |
| 253 | - | s.push(' '); | |
| 254 | - | s.push_str(&seg.amount); | |
| 255 | - | } | |
| 256 | - | s | |
| 257 | - | } | |
| 258 | - | ||
| 259 | 141 | /// Operator-edited runway figures, loaded once at startup. The | |
| 260 | 142 | /// live paying-creator counts come from the DB at request time and | |
| 261 | 143 | /// are NOT in this struct — see `db::creator_tiers::count_active_paying` | |
| @@ -288,14 +170,6 @@ impl RunwayConfig { | |||
| 288 | 170 | } | |
| 289 | 171 | } | |
| 290 | 172 | ||
| 291 | - | fn float_at(a: &Assumptions, key: &str) -> f64 { | |
| 292 | - | match a.get(key) { | |
| 293 | - | Some(LookupValue::Float(x)) => *x, | |
| 294 | - | Some(LookupValue::Int(n)) => *n as f64, | |
| 295 | - | other => panic!("expected number at {key}, got {other:?}"), | |
| 296 | - | } | |
| 297 | - | } | |
| 298 | - | ||
| 299 | 173 | fn int_at(a: &Assumptions, key: &str) -> i32 { | |
| 300 | 174 | match a.get(key) { | |
| 301 | 175 | Some(LookupValue::Int(n)) => i32::try_from(*n) | |
| @@ -318,52 +192,6 @@ mod tests { | |||
| 318 | 192 | ||
| 319 | 193 | const ASSUMPTIONS_PATH: &str = "docs/business/assumptions.toml"; | |
| 320 | 194 | ||
| 321 | - | /// Guards every key TierPrices reads. If a future toml edit removes one of | |
| 322 | - | /// these or flips its type, the panic in `from_assumptions` will fire at | |
| 323 | - | /// startup; this test catches it at PR time instead. | |
| 324 | - | #[test] | |
| 325 | - | fn cost_allocation_from_canonical_assumptions_sums_to_tier_price() { | |
| 326 | - | // Each row's six segments must sum to the standard tier price. | |
| 327 | - | // A future toml edit that breaks that invariant — say a typo in | |
| 328 | - | // `cost_allocation.big_files.storage` — would render a stacked | |
| 329 | - | // bar whose visible total disagrees with the headline price. | |
| 330 | - | // Catch the divergence at PR time. | |
| 331 | - | let a = Assumptions::load(ASSUMPTIONS_PATH).expect("load canonical toml"); | |
| 332 | - | let tp = TierPrices::from_assumptions(&a); | |
| 333 | - | let alloc = CostAllocation::from_assumptions(&a, &tp); | |
| 334 | - | assert_eq!(alloc.rows.len(), 4); | |
| 335 | - | ||
| 336 | - | let prices = [tp.basic_std, tp.small_files_std, tp.big_files_std, tp.everything_std]; | |
| 337 | - | for (row, &price) in alloc.rows.iter().zip(prices.iter()) { | |
| 338 | - | assert_eq!(row.tier_price, price); | |
| 339 | - | let sum_cents: i32 = row.segments.iter().map(|s| s.cents).sum(); | |
| 340 | - | assert_eq!( | |
| 341 | - | sum_cents, | |
| 342 | - | price * 100, | |
| 343 | - | "{tier} segments sum to {sum_cents}¢ but tier price is ${price} = {expected}¢", | |
| 344 | - | tier = row.tier_label, | |
| 345 | - | expected = price * 100, | |
| 346 | - | ); | |
| 347 | - | // Six canonical segments in canonical order. | |
| 348 | - | let kinds: Vec<&str> = row.segments.iter().map(|s| s.kind).collect(); | |
| 349 | - | assert_eq!( | |
| 350 | - | kinds, | |
| 351 | - | vec!["stripe", "storage", "support", "engineering", "reserves", "earnback"], | |
| 352 | - | ); | |
| 353 | - | } | |
| 354 | - | ||
| 355 | - | // Spot-check the per-segment values match what we agreed in the | |
| 356 | - | // economics doc. A drift here means the toml was edited without | |
| 357 | - | // a corresponding rationale update. | |
| 358 | - | let basic = &alloc.rows[0]; | |
| 359 | - | assert_eq!(basic.segments[0].amount, "$0.76"); // stripe | |
| 360 | - | assert_eq!(basic.segments[5].amount, "$1.84"); // earnback | |
| 361 | - | let everything = &alloc.rows[3]; | |
| 362 | - | assert_eq!(everything.segments[2].amount, "$5.00"); // support flat | |
| 363 | - | assert_eq!(everything.segments[3].amount, "$6.00"); // engineering flat | |
| 364 | - | assert_eq!(everything.segments[4].amount, "$7.50"); // reserves 12.5% of $60 | |
| 365 | - | } | |
| 366 | - | ||
| 367 | 195 | #[test] | |
| 368 | 196 | fn runway_config_loads_from_canonical_assumptions() { | |
| 369 | 197 | // The presence of the [runway] block is the only enforced thing — | |
| @@ -393,21 +221,6 @@ mod tests { | |||
| 393 | 221 | } | |
| 394 | 222 | ||
| 395 | 223 | #[test] | |
| 396 | - | fn cost_allocation_aria_label_names_every_segment() { | |
| 397 | - | // Screen readers get the full breakdown via aria-label since the | |
| 398 | - | // colored bar carries no native semantics. Pin the format. | |
| 399 | - | let a = Assumptions::load(ASSUMPTIONS_PATH).expect("load canonical toml"); | |
| 400 | - | let tp = TierPrices::from_assumptions(&a); | |
| 401 | - | let alloc = CostAllocation::from_assumptions(&a, &tp); | |
| 402 | - | let aria = &alloc.rows[0].aria_label; | |
| 403 | - | assert!(aria.starts_with("Basic tier $16/mo allocation: ")); | |
| 404 | - | for label in ["Stripe processing", "Storage", "Human support time", | |
| 405 | - | "Product engineering", "Reserves", "Earn-back (returned to creators)"] { | |
| 406 | - | assert!(aria.contains(label), "aria-label missing {label}: {aria}"); | |
| 407 | - | } | |
| 408 | - | } | |
| 409 | - | ||
| 410 | - | #[test] | |
| 411 | 224 | fn from_canonical_assumptions_populates_every_field() { | |
| 412 | 225 | let a = Assumptions::load(ASSUMPTIONS_PATH).expect("load canonical toml"); | |
| 413 | 226 | let p = TierPrices::from_assumptions(&a); |
| @@ -11158,70 +11158,6 @@ button.saved { border-color: var(--action); color: var(--action); } | |||
| 11158 | 11158 | .synckit-gauge-fill--warn { background: var(--warning); } | |
| 11159 | 11159 | .synckit-gauge-fill--danger { background: var(--danger); } | |
| 11160 | 11160 | ||
| 11161 | - | /* Cost-allocation widget on /pricing — "Where your tier fee goes". */ | |
| 11162 | - | .cost-allocation { margin-top: 0.5rem; } | |
| 11163 | - | .cost-allocation-intro { | |
| 11164 | - | font-size: 0.95rem; | |
| 11165 | - | opacity: 0.85; | |
| 11166 | - | margin-bottom: 1.5rem; | |
| 11167 | - | max-width: 60ch; | |
| 11168 | - | } | |
| 11169 | - | .cost-row { | |
| 11170 | - | display: grid; | |
| 11171 | - | grid-template-columns: 12rem 1fr; | |
| 11172 | - | gap: 1rem; | |
| 11173 | - | align-items: center; | |
| 11174 | - | margin-bottom: 1rem; | |
| 11175 | - | } | |
| 11176 | - | .cost-row-label { | |
| 11177 | - | font-family: var(--font-mono); | |
| 11178 | - | font-size: 0.9rem; | |
| 11179 | - | } | |
| 11180 | - | .cost-row-label strong { font-family: var(--font-heading); font-size: 1.05rem; display: block; } | |
| 11181 | - | .cost-bar { | |
| 11182 | - | display: flex; | |
| 11183 | - | height: 2.25rem; | |
| 11184 | - | border-radius: 4px; | |
| 11185 | - | overflow: hidden; | |
| 11186 | - | border: 1px solid var(--border, rgba(0,0,0,0.12)); | |
| 11187 | - | } | |
| 11188 | - | .cost-bar-seg { | |
| 11189 | - | display: flex; | |
| 11190 | - | align-items: center; | |
| 11191 | - | justify-content: center; | |
| 11192 | - | font-family: var(--font-mono); | |
| 11193 | - | font-size: 0.75rem; | |
| 11194 | - | color: #fff; | |
| 11195 | - | cursor: help; | |
| 11196 | - | padding: 0 0.25rem; | |
| 11197 | - | overflow: hidden; | |
| 11198 | - | white-space: nowrap; | |
| 11199 | - | } | |
| 11200 | - | .cost-bar-seg-stripe { background: #5b6f8a; } | |
| 11201 | - | .cost-bar-seg-storage { background: #7d8c5e; } | |
| 11202 | - | .cost-bar-seg-support { background: #8b6b4a; } | |
| 11203 | - | .cost-bar-seg-engineering { background: #6b4a8b; } | |
| 11204 | - | .cost-bar-seg-reserves { background: #4a6b8b; } | |
| 11205 | - | .cost-bar-seg-earnback { background: #8b8b4a; } | |
| 11206 | - | .cost-legend { | |
| 11207 | - | display: flex; | |
| 11208 | - | flex-wrap: wrap; | |
| 11209 | - | gap: 0.5rem 1.25rem; | |
| 11210 | - | margin-top: 1.5rem; | |
| 11211 | - | font-size: 0.85rem; | |
| 11212 | - | } | |
| 11213 | - | .cost-legend-item { display: flex; align-items: center; gap: 0.4rem; } | |
| 11214 | - | .cost-legend-swatch { | |
| 11215 | - | display: inline-block; | |
| 11216 | - | width: 0.85rem; | |
| 11217 | - | height: 0.85rem; | |
| 11218 | - | border-radius: 2px; | |
| 11219 | - | } | |
| 11220 | - | @media (max-width: 640px) { | |
| 11221 | - | .cost-row { grid-template-columns: 1fr; gap: 0.4rem; } | |
| 11222 | - | .cost-bar-seg { font-size: 0; } | |
| 11223 | - | } | |
| 11224 | - | ||
| 11225 | 11161 | /* ============================================================================ | |
| 11226 | 11162 | Carousel -- composable click-through widget (partials/carousel.html). | |
| 11227 | 11163 | One frame visible at a time; viewer-driven (prev/next, dots, arrow keys). |
| @@ -7,8 +7,8 @@ | |||
| 7 | 7 | inbound links. | |
| 8 | 8 | ||
| 9 | 9 | Prose mirrors the previous economics.md content (deleted in the same | |
| 10 | - | commit). To edit prose, edit this file. To edit the cost-allocation | |
| 11 | - | model or the runway figure, edit docs/business/assumptions.toml. #} | |
| 10 | + | commit). To edit prose, edit this file. To edit the runway figure, edit | |
| 11 | + | docs/business/assumptions.toml. #} | |
| 12 | 12 | {% extends "base.html" %} | |
| 13 | 13 | ||
| 14 | 14 | {% block title %}Platform Economics - Makenot.work{% endblock %} | |
| @@ -50,8 +50,6 @@ | |||
| 50 | 50 | ||
| 51 | 51 | <p><strong>Returned to creators.</strong> Surplus beyond what cost-to-deliver and platform overhead require is earmarked to come back to you as earn-back credit. We've committed to launching that program no later than 2027-01-01.</p> | |
| 52 | 52 | ||
| 53 | - | <p>See the full breakdown on the <a href="/pricing#cost-allocation">pricing page</a> — every tier fee broken into Stripe processing, storage, human support time, product engineering, reserves, and earn-back surplus.</p> | |
| 54 | - | ||
| 55 | 53 | <p>No surplus goes to investors, shareholders, dividends, executive bonuses, paid acquisition, or marketing spend. We have none of those things.</p> | |
| 56 | 54 | ||
| 57 | 55 | <hr> |
| @@ -66,36 +66,6 @@ | |||
| 66 | 66 | </div> | |
| 67 | 67 | </div> | |
| 68 | 68 | ||
| 69 | - | <div class="tier-section cost-allocation" id="cost-allocation"> | |
| 70 | - | <h2 class="section-label">Where your tier fee goes</h2> | |
| 71 | - | <p class="cost-allocation-intro">You're paying for a platform built to be fair and built to last. Here's where each monthly fee goes, averaged across creators on that tier. Hover any segment for detail.</p> | |
| 72 | - | <p class="cost-allocation-founder-note">Founder-rate creators pay exactly 50% of the standard tier. Per-creator fixed costs (Support, Engineering, Storage, Stripe) stay the same; the discount comes out of Earn-back and Reserves. The shape is unchanged, the totals halve.</p> | |
| 73 | - | ||
| 74 | - | {# Every dollar here round-trips through `cost_allocation.*` in | |
| 75 | - | `docs/business/assumptions.toml`. Tests in `tier_prices::tests` | |
| 76 | - | pin the per-segment values; tooltip copy lives in the Rust | |
| 77 | - | builder. #} | |
| 78 | - | {% for row in cost_allocation.rows %} | |
| 79 | - | <div class="cost-row"> | |
| 80 | - | <div class="cost-row-label"><strong>{{ row.tier_label }}</strong>${{ row.tier_price }}/mo</div> | |
| 81 | - | <div class="cost-bar" role="img" aria-label="{{ row.aria_label }}"> | |
| 82 | - | {% for seg in row.segments %} | |
| 83 | - | <div class="cost-bar-seg cost-bar-seg-{{ seg.kind }}" style="flex: {{ seg.cents }}" title="{{ seg.tooltip }}">{{ seg.amount }}</div> | |
| 84 | - | {% endfor %} | |
| 85 | - | </div> | |
| 86 | - | </div> | |
| 87 | - | {% endfor %} | |
| 88 | - | ||
| 89 | - | <div class="cost-legend" aria-hidden="true"> | |
| 90 | - | <span class="cost-legend-item"><span class="cost-legend-swatch cost-bar-seg-stripe"></span>Stripe processing</span> | |
| 91 | - | <span class="cost-legend-item"><span class="cost-legend-swatch cost-bar-seg-storage"></span>Storage (typical fill)</span> | |
| 92 | - | <span class="cost-legend-item"><span class="cost-legend-swatch cost-bar-seg-support"></span>Human support time</span> | |
| 93 | - | <span class="cost-legend-item"><span class="cost-legend-swatch cost-bar-seg-engineering"></span>Product engineering</span> | |
| 94 | - | <span class="cost-legend-item"><span class="cost-legend-swatch cost-bar-seg-reserves"></span>Reserves</span> | |
| 95 | - | <span class="cost-legend-item"><span class="cost-legend-swatch cost-bar-seg-earnback"></span>Earn-back (returned to creators)</span> | |
| 96 | - | </div> | |
| 97 | - | </div> | |
| 98 | - | ||
| 99 | 69 | <div class="landing-cta"> | |
| 100 | 70 | <a class="btn-primary btn--large" href="/join">Join the Alpha</a> | |
| 101 | 71 | </div> |