Skip to main content

max / makenotwork

16.3 KB · 380 lines History Blame Raw
1 {% extends "base.html" %}
2
3 {% block title %}Pricing Calculator - Makenot.work{% endblock %}
4
5 {% block body_attrs %} class="centered-page"{% endblock %}
6
7 {% block content %}
8 <div class="landing-hero pricing-page">
9 <h1 class="page-title">Pricing Calculator<span class="dot">.</span></h1>
10 <p class="tagline">See what you keep on every sale<span class="dot">.</span></p>
11
12 <div class="tier-section">
13 <h2 class="section-label">Monthly revenue</h2>
14 <div class="pricing-input-wrap">
15 <span class="pricing-currency">$</span>
16 <input type="number" id="revenue" class="pricing-input" value="1000" min="0" max="999999" step="50" aria-label="Monthly revenue in dollars">
17 <span class="pricing-period">/mo</span>
18 </div>
19 </div>
20
21 <div class="tier-section">
22 <h2 class="section-label">Your content tier</h2>
23 <p class="tier-intro">Every tier is the complete platform: profile, project pages, forum, discovery, memberships, analytics, full data export. The tier picks the file-size envelope, not the feature set.</p>
24 <div class="tier-selector" role="radiogroup" aria-label="Content tier">
25 <label class="tier-card tier-option is-selected">
26 <input type="radio" name="tier" value="{{ tier_prices.basic_std }}" checked>
27 <div class="tier-name">Basic</div>
28 <div class="tier-price">${{ tier_prices.basic_std }}/mo</div>
29 <div class="tier-desc">{{ tier_prices.basic_per_file }}/file, {{ tier_prices.basic_total }} total. Fits text, blogs, newsletters.</div>
30 </label>
31 <label class="tier-card tier-option">
32 <input type="radio" name="tier" value="{{ tier_prices.small_files_std }}">
33 <div class="tier-name">Small Files</div>
34 <div class="tier-price">${{ tier_prices.small_files_std }}/mo</div>
35 <div class="tier-desc">{{ tier_prices.small_files_per_file }}/file, {{ tier_prices.small_files_total }} total. Fits audio, plugins, binaries.</div>
36 </label>
37 <label class="tier-card tier-option">
38 <input type="radio" name="tier" value="{{ tier_prices.big_files_std }}">
39 <div class="tier-name">Big Files</div>
40 <div class="tier-price">${{ tier_prices.big_files_std }}/mo</div>
41 <div class="tier-desc">{{ tier_prices.big_files_per_file }}/file, {{ tier_prices.big_files_total }} total. Fits video, games, large software.</div>
42 </label>
43 <label class="tier-card tier-option">
44 <input type="radio" name="tier" value="{{ tier_prices.everything_std }}">
45 <div class="tier-name">Everything</div>
46 <div class="tier-price">${{ tier_prices.everything_std }}/mo</div>
47 <div class="tier-desc">Big Files envelope plus first access to high-cost features as they ship.</div>
48 </label>
49 </div>
50 </div>
51
52 <div class="tier-section" id="results">
53 <h2 class="section-label">You keep</h2>
54 <div class="mnw-summary">
55 <div class="mnw-keep" id="mnw-keep">$943.00</div>
56 <div class="mnw-detail" id="mnw-detail">of every $1,000 after processing fees (~3%) and ${{ tier_prices.basic_std }}/mo membership</div>
57 </div>
58 </div>
59
60 <div class="tier-section">
61 <h2 class="section-label">Platform comparison</h2>
62 <table class="data-table" id="comparison-table" aria-label="Platform fee comparison">
63 <thead>
64 <tr>
65 <th>Platform</th>
66 <th>Fee structure</th>
67 <th>You keep</th>
68 <th>vs MNW</th>
69 </tr>
70 </thead>
71 <tbody id="comparison-body">
72 </tbody>
73 </table>
74 <p class="pricing-disclaimer">Fee structures sourced from each platform's public pricing page. Actual fees may vary by plan, region, or payment method. Verify on each platform's site before making decisions.</p>
75 <p class="pricing-disclaimer"><strong>Selling small-ticket items?</strong> Payment processors charge a fixed fee per transaction (~$0.30) that hits harder on $1-5 sales. This is an industry-wide constraint, not specific to any platform. Bundling items into collections lets your fans buy in groups at a single transaction cost instead of paying per-item processing fees.</p>
76 </div>
77
78 <div class="tier-section hidden" id="breakeven-section">
79 <div class="breakeven-note" id="breakeven-note"></div>
80 </div>
81
82 <div class="tier-section cost-allocation" id="cost-allocation">
83 <h2 class="section-label">Where your tier fee goes</h2>
84 <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>
85 <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>
86
87 {# Every dollar here round-trips through `cost_allocation.*` in
88 `docs/business/assumptions.toml`. Tests in `tier_prices::tests`
89 pin the per-segment values; tooltip copy lives in the Rust
90 builder. #}
91 {% for row in cost_allocation.rows %}
92 <div class="cost-row">
93 <div class="cost-row-label"><strong>{{ row.tier_label }}</strong>${{ row.tier_price }}/mo</div>
94 <div class="cost-bar" role="img" aria-label="{{ row.aria_label }}">
95 {% for seg in row.segments %}
96 <div class="cost-bar-seg cost-bar-seg-{{ seg.kind }}" style="flex: {{ seg.cents }}" title="{{ seg.tooltip }}">{{ seg.amount }}</div>
97 {% endfor %}
98 </div>
99 </div>
100 {% endfor %}
101
102 <div class="cost-legend" aria-hidden="true">
103 <span class="cost-legend-item"><span class="cost-legend-swatch cost-bar-seg-stripe"></span>Stripe processing</span>
104 <span class="cost-legend-item"><span class="cost-legend-swatch cost-bar-seg-storage"></span>Storage (typical fill)</span>
105 <span class="cost-legend-item"><span class="cost-legend-swatch cost-bar-seg-support"></span>Human support time</span>
106 <span class="cost-legend-item"><span class="cost-legend-swatch cost-bar-seg-engineering"></span>Product engineering</span>
107 <span class="cost-legend-item"><span class="cost-legend-swatch cost-bar-seg-reserves"></span>Reserves</span>
108 <span class="cost-legend-item"><span class="cost-legend-swatch cost-bar-seg-earnback"></span>Earn-back (returned to creators)</span>
109 </div>
110 </div>
111
112 <div class="landing-cta">
113 <a class="btn-primary btn--large" href="/join">Join the Alpha</a>
114 </div>
115
116 <div class="secondary-links">
117 <a href="/">Home</a>
118 <a href="/discover">Browse as guest</a>
119 </div>
120 </div>
121 {% endblock %}
122
123 {% block scripts %}
124 <script>
125 (function() {
126 'use strict';
127
128 var STRIPE_PERCENT = 0.029;
129 var STRIPE_FIXED = 0.30;
130
131 // Average transactions per month at a given revenue level.
132 // Assumes ~$25 average transaction for simplicity.
133 function estimateTransactions(revenue) {
134 if (revenue <= 0) return 0;
135 return Math.max(1, Math.round(revenue / 25));
136 }
137
138 function stripeNet(revenue) {
139 if (revenue <= 0) return 0;
140 var txns = estimateTransactions(revenue);
141 return revenue - (revenue * STRIPE_PERCENT) - (txns * STRIPE_FIXED);
142 }
143
144 var competitors = [
145 {
146 name: 'Patreon',
147 url: 'https://www.patreon.com/pricing',
148 fee: '10% + ~3% + $0.30',
149 calc: function(rev) {
150 if (rev <= 0) return 0;
151 var txns = estimateTransactions(rev);
152 var afterPlatform = rev * 0.90;
153 return afterPlatform - (afterPlatform * STRIPE_PERCENT) - (txns * STRIPE_FIXED);
154 }
155 },
156 {
157 name: 'Gumroad',
158 url: 'https://gumroad.com/pricing',
159 fee: '10% + $0.50/tx + ~3%',
160 calc: function(rev) {
161 if (rev <= 0) return 0;
162 var txns = estimateTransactions(rev);
163 var afterPlatform = rev * 0.90 - (txns * 0.50);
164 return afterPlatform - (afterPlatform * STRIPE_PERCENT);
165 }
166 },
167 {
168 name: 'Bandcamp',
169 url: 'https://bandcamp.com/pricing',
170 fee: '15% (10% after $5k) + ~3%',
171 calc: function(rev) {
172 if (rev <= 0) return 0;
173 var rate = rev > 5000 ? 0.10 : 0.15;
174 var afterPlatform = rev * (1 - rate);
175 return afterPlatform - (afterPlatform * STRIPE_PERCENT);
176 }
177 },
178 {
179 name: 'Ko-fi Free',
180 url: 'https://ko-fi.com/pricing',
181 fee: '0% tips, 5% shop/memberships + ~3%',
182 calc: function(rev) {
183 if (rev <= 0) return 0;
184 var afterPlatform = rev * 0.95;
185 return afterPlatform - (afterPlatform * STRIPE_PERCENT);
186 }
187 },
188 {
189 name: 'Ko-fi Gold',
190 url: 'https://ko-fi.com/pricing',
191 fee: '$12/mo + 0% + ~3%',
192 calc: function(rev) {
193 if (rev <= 0) return -12;
194 return stripeNet(rev) - 12;
195 }
196 },
197 {
198 name: 'Itch.io',
199 url: 'https://itch.io/docs/creators/faq',
200 fee: '10% default + ~3%',
201 calc: function(rev) {
202 if (rev <= 0) return 0;
203 var afterPlatform = rev * 0.90;
204 return afterPlatform - (afterPlatform * STRIPE_PERCENT);
205 }
206 },
207 {
208 name: 'Lemon Squeezy',
209 url: 'https://www.lemonsqueezy.com/pricing',
210 fee: '5% + $0.50/tx (incl. processing)',
211 calc: function(rev) {
212 if (rev <= 0) return 0;
213 var txns = estimateTransactions(rev);
214 return rev * 0.95 - (txns * 0.50);
215 }
216 },
217 {
218 name: 'Buy Me a Coffee',
219 url: 'https://www.buymeacoffee.com/about',
220 fee: '5% + ~3%',
221 calc: function(rev) {
222 if (rev <= 0) return 0;
223 var afterPlatform = rev * 0.95;
224 return afterPlatform - (afterPlatform * STRIPE_PERCENT);
225 }
226 },
227 {
228 name: 'Substack',
229 url: 'https://substack.com/going-paid',
230 fee: '10% on paid subs',
231 calc: function(rev) {
232 if (rev <= 0) return 0;
233 var afterPlatform = rev * 0.90;
234 return afterPlatform - (afterPlatform * STRIPE_PERCENT);
235 }
236 }
237 ];
238
239 function fmt(n) {
240 return '$' + n.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
241 }
242
243 function fmtWhole(n) {
244 return '$' + Math.round(n).toLocaleString();
245 }
246
247 function update() {
248 var revenueInput = document.getElementById('revenue');
249 var revenue = parseFloat(revenueInput.value) || 0;
250
251 var tierRadios = document.querySelectorAll('input[name="tier"]');
252 // Default falls back to the Basic-tier radio value; templates render the canonical price.
253 var tierCost = {{ tier_prices.basic_std }};
254 for (var i = 0; i < tierRadios.length; i++) {
255 if (tierRadios[i].checked) {
256 tierCost = parseInt(tierRadios[i].value, 10);
257 break;
258 }
259 }
260
261 // MNW net: revenue minus processing fees minus monthly membership
262 var mnwNet = stripeNet(revenue) - tierCost;
263 if (revenue <= 0) mnwNet = -tierCost;
264
265 document.getElementById('mnw-keep').textContent = fmt(Math.max(0, mnwNet));
266 document.getElementById('mnw-detail').textContent =
267 'of every ' + fmtWhole(revenue) + ' after processing fees (~3%) and ' + fmt(tierCost) + '/mo membership';
268
269 // Build comparison rows sorted by net (descending)
270 var rows = [];
271 rows.push({
272 name: 'Makenot.work',
273 fee: fmt(tierCost) + '/mo flat + ~3% processing',
274 net: mnwNet,
275 isMnw: true
276 });
277
278 for (var j = 0; j < competitors.length; j++) {
279 var c = competitors[j];
280 rows.push({
281 name: c.name,
282 url: c.url,
283 fee: c.fee,
284 net: c.calc(revenue),
285 isMnw: false
286 });
287 }
288
289 rows.sort(function(a, b) { return b.net - a.net; });
290
291 var tbody = document.getElementById('comparison-body');
292 tbody.innerHTML = '';
293
294 var breakevenMessages = [];
295
296 for (var k = 0; k < rows.length; k++) {
297 var row = rows[k];
298 var tr = document.createElement('tr');
299 if (row.isMnw) tr.className = 'highlight';
300
301 var tdName = document.createElement('td');
302 if (row.isMnw) {
303 tdName.textContent = row.name;
304 tdName.style.fontWeight = '600';
305 } else {
306 var link = document.createElement('a');
307 link.href = row.url;
308 link.textContent = row.name;
309 link.target = '_blank';
310 link.rel = 'noopener';
311 tdName.appendChild(link);
312 }
313 tr.appendChild(tdName);
314
315 var tdFee = document.createElement('td');
316 tdFee.textContent = row.fee;
317 tr.appendChild(tdFee);
318
319 var tdKeep = document.createElement('td');
320 tdKeep.textContent = revenue > 0 ? fmt(Math.max(0, row.net)) : '--';
321 tr.appendChild(tdKeep);
322
323 var tdDiff = document.createElement('td');
324 if (row.isMnw) {
325 tdDiff.textContent = '--';
326 } else if (revenue > 0) {
327 var diff = mnwNet - row.net;
328 if (diff > 0.005) {
329 tdDiff.textContent = '+' + fmt(diff);
330 tdDiff.className = 'savings-negative';
331 } else if (diff < -0.005) {
332 tdDiff.textContent = fmt(diff);
333 tdDiff.className = 'savings-positive';
334 breakevenMessages.push(row.name);
335 } else {
336 tdDiff.textContent = '$0.00';
337 }
338 } else {
339 tdDiff.textContent = '--';
340 }
341 tr.appendChild(tdDiff);
342
343 tbody.appendChild(tr);
344 }
345
346 // Breakeven note
347 var breakevenSection = document.getElementById('breakeven-section');
348 var breakevenNote = document.getElementById('breakeven-note');
349 if (breakevenMessages.length > 0 && revenue > 0) {
350 breakevenSection.classList.remove('hidden');
351 breakevenNote.textContent =
352 'At ' + fmtWhole(revenue) + '/mo, some platforms with percentage-only fees cost less than MNW\'s flat rate. '
353 + 'MNW becomes cheaper as your revenue grows.';
354 } else {
355 breakevenSection.classList.add('hidden');
356 }
357 }
358
359 // Tier selector visual state. Listen on the radio inputs so keyboard
360 // navigation (arrow keys within the radiogroup) updates the styling.
361 var tierInputs = document.querySelectorAll('.tier-option input[type="radio"]');
362 var tierLabels = document.querySelectorAll('.tier-option');
363 for (var i = 0; i < tierInputs.length; i++) {
364 tierInputs[i].addEventListener('change', function() {
365 for (var j = 0; j < tierLabels.length; j++) {
366 tierLabels[j].classList.remove('is-selected');
367 }
368 this.closest('.tier-option').classList.add('is-selected');
369 update();
370 });
371 }
372
373 document.getElementById('revenue').addEventListener('input', update);
374
375 // Initial calculation
376 update();
377 })();
378 </script>
379 {% endblock %}
380