From 61e5aa774dd0cd4ad39e854616d9ae4f1850039c Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Thu, 30 Jul 2026 20:46:45 +0200 Subject: [PATCH 1/7] docs: add accounting presentation brief The objective for the pitch: show an incubator collective the value of accounting practices via an intro to principles + a worked mock project, with a Beancount/Fava ledger backing the numbers. Co-Authored-By: Claude Opus 4.8 (1M context) --- Accounting-Presentation.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 Accounting-Presentation.md diff --git a/Accounting-Presentation.md b/Accounting-Presentation.md new file mode 100644 index 0000000..9aeab22 --- /dev/null +++ b/Accounting-Presentation.md @@ -0,0 +1,24 @@ +# Accounting Presentation + +Our goal here is to present to an incubator collective the value of accounting practices which they currently are not using. + +There are currently two members of the accounting team: Pat (software) and Rayan (accountant) + +Our goal is to give a quick intro into accounting principals and then show a mock evolution of a project being done with proper accounting processes. That will be demonstrated using visualizations tracking the accounting books related to to a project: + +Initial Investment money received and distributed to accounts as a function of budget described in investment pitch +In those accounts include cost of materials, salaries, equity, etc so that at any point in time someone may see what the state of the organization. +We want to visualize a common tech project trajectory and track a pretend ledger over time to show how accounting is beneficial. E.g., what does it look like if the hardware team needs $5,000 to purchase servers from an accounting perspective. + +Because this is a collective, there are a lot of individuals who have balances with the entity who want to be able to see where they stand. Some of those balances with the organization could be liabilities of the organization to the user (e.g., expenses), some could be equity that gives the user claim to future profit, etc + +We also provide the user an application. Each entity will have its own wallet and each entity will also have users/employees underneath it. Therefore when an employee needs to make a purchase, they either have direct access to the account allowing them to purchase what they need, or they know who to go to to ask for money and confirm that it can also be budgeted for. These employees will also have access to a local cash machine that works exclusively with the system for them to withdraw from the accounts to get cash (say somewhere on the order of $1,000) or they would use a company credit card (that system still has to be determined) + +This software has a feature that allows people to also log expenses on behalf of the organization. E.g., let's say you are a member and you go out and buy gas for the organization car, you can easily log that expense so that you know you will be paid back + +## Main value proposals to users/managers +- for the individual: ability to see where you stand +- for the team: efficient flow of money when and where it needs to go + +# NOTE +We are using github.com/beancount/beancount and beancount/fava in our stack. Create mock data tracking the evolution of our mock project above so we can open it up in fava to show what the real reporting software provides to supplement the simplified visualizations in the pitch deck From da33c0eec763553d2174d7658ad43dd8b116f46e Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Thu, 30 Jul 2026 20:46:45 +0200 Subject: [PATCH 2/7] chore: enable vue-demi build script in pnpm-workspace The boilerplate shipped an invalid `allowBuilds: { vue-demi: "set this to true or false" }` placeholder, which made pnpm's pre-run dependency check fail with ERR_PNPM_IGNORED_BUILDS and blocked `pnpm dev` / `pnpm build`. Co-Authored-By: Claude Opus 4.8 (1M context) --- pnpm-workspace.yaml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 pnpm-workspace.yaml diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..ae1acad --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +allowBuilds: + vue-demi: true From 6f330d49d4540d6b0c16b3446b6fc8225121f454 Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Thu, 30 Jul 2026 20:46:55 +0200 Subject: [PATCH 3/7] feat(ledger): add Project Lantern beancount ledger + fava launcher A plain-text Beancount ledger tracking the mock project's first seven months (seed round, monthly burn, a $5k capitalized server purchase, a member reimbursement, a contributor equity grant, first pre-orders), plus budget directives and milestone events. Passes `bean-check`. `ledger/run-fava.sh` runs Fava from a local venv, working around the system package's missing `beanquery` dependency; `.venv/` is gitignored. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 3 + ledger/lantern.beancount | 267 +++++++++++++++++++++++++++++++++++++++ ledger/run-fava.sh | 19 +++ 3 files changed, 289 insertions(+) create mode 100644 ledger/lantern.beancount create mode 100755 ledger/run-fava.sh diff --git a/.gitignore b/.gitignore index ad5f2f7..5fb81ec 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,7 @@ dist-ssr *.log pnpm-debug.log* +# python venv used to run Fava (see ledger/run-fava.sh) +.venv/ + .claude/ diff --git a/ledger/lantern.beancount b/ledger/lantern.beancount new file mode 100644 index 0000000..1506332 --- /dev/null +++ b/ledger/lantern.beancount @@ -0,0 +1,267 @@ +;; ============================================================================ +;; Project Lantern — mock ledger for the accounting pitch deck +;; ---------------------------------------------------------------------------- +;; A hardware project inside the Atitlán incubator collective. This ledger +;; tracks the project's first seven months (Jan–Jul 2026) so it can be opened +;; in Fava (`fava ledger/lantern.beancount`) to show the "real" reporting that +;; sits behind the simplified visualizations in the slide deck. +;; +;; Story beats mirrored in the deck: +;; - Seed investment received and put to work as budgeted +;; - Salaries, rent, utilities, software — the monthly burn +;; - A $5,000 server purchase (capitalized as an ASSET, not an expense) +;; - A member fronts cash for fuel and is owed a reimbursement (a LIABILITY) +;; - A contributor earns equity instead of cash (a claim on future profit) +;; - First pre-order revenue starts coming in +;; ============================================================================ + +option "title" "Project Lantern — Atitlán Collective" +option "operating_currency" "USD" + +;; ---------------------------------------------------------------------------- +;; Chart of accounts +;; ---------------------------------------------------------------------------- +2026-01-01 open Assets:Bank:Checking USD +2026-01-01 open Assets:Cash:PettyCash USD +2026-01-01 open Assets:Equipment:Servers USD + +2026-01-01 open Liabilities:CreditCard USD +2026-01-01 open Liabilities:Reimbursements:Alice USD + +2026-01-01 open Equity:Investors:Seed USD +2026-01-01 open Equity:Contributors:Bob USD + +2026-01-01 open Income:PreOrders USD + +2026-01-01 open Expenses:Salaries:Pat USD +2026-01-01 open Expenses:Salaries:Rayan USD +2026-01-01 open Expenses:Salaries:Sam USD +2026-01-01 open Expenses:Contributed:Engineering USD +2026-01-01 open Expenses:Materials USD +2026-01-01 open Expenses:Operations:Rent USD +2026-01-01 open Expenses:Operations:Utilities USD +2026-01-01 open Expenses:Operations:Software USD +2026-01-01 open Expenses:Travel:Fuel USD + +;; ---------------------------------------------------------------------------- +;; Budget — the plan the seed money was raised against (Fava reads these) +;; ---------------------------------------------------------------------------- +2026-01-01 custom "budget" Expenses:Salaries:Pat "monthly" 2500.00 USD +2026-01-01 custom "budget" Expenses:Salaries:Rayan "monthly" 1500.00 USD +2026-01-01 custom "budget" Expenses:Salaries:Sam "monthly" 2000.00 USD +2026-01-01 custom "budget" Expenses:Materials "monthly" 2500.00 USD +2026-01-01 custom "budget" Expenses:Operations:Rent "monthly" 800.00 USD +2026-01-01 custom "budget" Expenses:Operations:Utilities "monthly" 200.00 USD +2026-01-01 custom "budget" Expenses:Operations:Software "monthly" 150.00 USD + +;; ---------------------------------------------------------------------------- +;; Milestones (shown on Fava's events page) +;; ---------------------------------------------------------------------------- +2026-01-15 event "milestone" "Seed round closed — $100k in the bank" +2026-03-18 event "milestone" "Server cluster online (capital asset)" +2026-06-15 event "milestone" "First customer pre-orders received" + +;; ============================================================================ +;; JANUARY 2026 — the money arrives and is put to work +;; ============================================================================ + +2026-01-15 * "Atitlán Ventures" "Seed round — initial investment received" + Assets:Bank:Checking 100000.00 USD + Equity:Investors:Seed + +2026-01-16 * "Coworking Tikal" "January workspace" + Expenses:Operations:Rent 800.00 USD + Assets:Bank:Checking + +2026-01-16 * "Utilities co-op" "Power + internet" + Expenses:Operations:Utilities 200.00 USD + Assets:Bank:Checking + +2026-01-16 * "SaaS bundle" "Design + CAD + hosting subscriptions" + Expenses:Operations:Software 150.00 USD + Assets:Bank:Checking + +2026-01-31 * "Payroll" "January salaries" + Expenses:Salaries:Pat 2500.00 USD + Expenses:Salaries:Rayan 1500.00 USD + Expenses:Salaries:Sam 2000.00 USD + Assets:Bank:Checking + +;; ============================================================================ +;; FEBRUARY 2026 — petty cash float + first prototype parts on the card +;; ============================================================================ + +2026-02-02 * "Cash machine" "Withdraw petty-cash float for on-site purchases" + Assets:Cash:PettyCash 1000.00 USD + Assets:Bank:Checking + +2026-02-03 * "Coworking Tikal" "February workspace" + Expenses:Operations:Rent 800.00 USD + Assets:Bank:Checking + +2026-02-03 * "Utilities co-op" "Power + internet" + Expenses:Operations:Utilities 200.00 USD + Assets:Bank:Checking + +2026-02-03 * "SaaS bundle" "Subscriptions" + Expenses:Operations:Software 150.00 USD + Assets:Bank:Checking + +2026-02-10 * "Parts supplier" "Prototype components (company card)" + Expenses:Materials 3000.00 USD + Liabilities:CreditCard + +2026-02-28 * "Payroll" "February salaries" + Expenses:Salaries:Pat 2500.00 USD + Expenses:Salaries:Rayan 1500.00 USD + Expenses:Salaries:Sam 2000.00 USD + Assets:Bank:Checking + +;; ============================================================================ +;; MARCH 2026 — settle the card, then the $5,000 server purchase +;; ============================================================================ + +2026-03-03 * "Coworking Tikal" "March workspace" + Expenses:Operations:Rent 800.00 USD + Assets:Bank:Checking + +2026-03-03 * "Utilities co-op" "Power + internet" + Expenses:Operations:Utilities 200.00 USD + Assets:Bank:Checking + +2026-03-03 * "SaaS bundle" "Subscriptions" + Expenses:Operations:Software 150.00 USD + Assets:Bank:Checking + +2026-03-05 * "Company card" "Pay off February card balance" + Liabilities:CreditCard 3000.00 USD + Assets:Bank:Checking + +;; The headline example from the deck: the hardware team needs $5,000 for +;; servers. It is NOT an expense — it buys a durable asset, so net worth is +;; unchanged the instant it happens: cash down $5k, equipment up $5k. +2026-03-18 * "Rack & Stack Ltd" "Server cluster for build/test farm — CAPITAL PURCHASE" + Assets:Equipment:Servers 5000.00 USD + Assets:Bank:Checking + +2026-03-31 * "Payroll" "March salaries" + Expenses:Salaries:Pat 2500.00 USD + Expenses:Salaries:Rayan 1500.00 USD + Expenses:Salaries:Sam 2000.00 USD + Assets:Bank:Checking + +;; ============================================================================ +;; APRIL 2026 — a member fronts fuel money (owed back), more prototype parts +;; ============================================================================ + +2026-04-02 * "Coworking Tikal" "April workspace" + Expenses:Operations:Rent 800.00 USD + Assets:Bank:Checking + +2026-04-02 * "Utilities co-op" "Power + internet" + Expenses:Operations:Utilities 200.00 USD + Assets:Bank:Checking + +2026-04-02 * "SaaS bundle" "Subscriptions" + Expenses:Operations:Software 150.00 USD + Assets:Bank:Checking + +;; Alice pays for fuel out of her own pocket. The org now OWES her — that debt +;; is a liability the moment it is logged, so she can see exactly where she +;; stands without waiting for anyone's memory. +2026-04-05 * "Alice" "Fuel for the org vehicle — fronted by member" + Expenses:Travel:Fuel 65.00 USD + Liabilities:Reimbursements:Alice + +2026-04-12 * "Parts supplier" "Second prototype revision" + Expenses:Materials 4500.00 USD + Assets:Bank:Checking + +;; Alice is paid back — the liability clears. +2026-04-20 * "Alice" "Reimburse fronted fuel expense" + Liabilities:Reimbursements:Alice 65.00 USD + Assets:Bank:Checking + +2026-04-30 * "Payroll" "April salaries" + Expenses:Salaries:Pat 2500.00 USD + Expenses:Salaries:Rayan 1500.00 USD + Expenses:Salaries:Sam 2000.00 USD + Assets:Bank:Checking + +;; ============================================================================ +;; MAY 2026 — a contributor takes equity instead of cash +;; ============================================================================ + +2026-05-04 * "Coworking Tikal" "May workspace" + Expenses:Operations:Rent 800.00 USD + Assets:Bank:Checking + +2026-05-04 * "Utilities co-op" "Power + internet" + Expenses:Operations:Utilities 200.00 USD + Assets:Bank:Checking + +2026-05-04 * "SaaS bundle" "Subscriptions" + Expenses:Operations:Software 150.00 USD + Assets:Bank:Checking + +;; Bob contributes engineering work but is paid in EQUITY rather than cash. +;; No money leaves the bank; instead Bob gains a claim on future profit. +2026-05-01 * "Bob" "Contributed engineering — settled as equity grant" + Expenses:Contributed:Engineering 3000.00 USD + Equity:Contributors:Bob + +2026-05-20 * "Parts supplier" "Pre-production batch" + Expenses:Materials 6000.00 USD + Assets:Bank:Checking + +2026-05-29 * "Payroll" "May salaries" + Expenses:Salaries:Pat 2500.00 USD + Expenses:Salaries:Rayan 1500.00 USD + Expenses:Salaries:Sam 2000.00 USD + Assets:Bank:Checking + +;; ============================================================================ +;; JUNE 2026 — the first revenue arrives +;; ============================================================================ + +2026-06-02 * "Coworking Tikal" "June workspace" + Expenses:Operations:Rent 800.00 USD + Assets:Bank:Checking + +2026-06-02 * "Utilities co-op" "Power + internet" + Expenses:Operations:Utilities 200.00 USD + Assets:Bank:Checking + +2026-06-02 * "SaaS bundle" "Subscriptions" + Expenses:Operations:Software 150.00 USD + Assets:Bank:Checking + +2026-06-15 * "Early customers" "First pre-order deposits" + Assets:Bank:Checking 4000.00 USD + Income:PreOrders + +2026-06-30 * "Payroll" "June salaries" + Expenses:Salaries:Pat 2500.00 USD + Expenses:Salaries:Rayan 1500.00 USD + Expenses:Salaries:Sam 2000.00 USD + Assets:Bank:Checking + +;; ============================================================================ +;; JULY 2026 — steady state; more pre-orders +;; ============================================================================ + +2026-07-02 * "Coworking Tikal" "July workspace" + Expenses:Operations:Rent 800.00 USD + Assets:Bank:Checking + +2026-07-02 * "Utilities co-op" "Power + internet" + Expenses:Operations:Utilities 200.00 USD + Assets:Bank:Checking + +2026-07-02 * "SaaS bundle" "Subscriptions" + Expenses:Operations:Software 150.00 USD + Assets:Bank:Checking + +2026-07-10 * "Early customers" "Second pre-order batch" + Assets:Bank:Checking 2500.00 USD + Income:PreOrders diff --git a/ledger/run-fava.sh b/ledger/run-fava.sh new file mode 100755 index 0000000..0fa1b76 --- /dev/null +++ b/ledger/run-fava.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# Launch Fava for the Project Lantern ledger. +# +# The system Fava (pacman) is missing its `beanquery` dependency, so we run it +# from a local venv that reuses system site-packages and adds only what's +# missing. First run creates the venv; afterwards it's instant. +set -euo pipefail +cd "$(dirname "$0")/.." + +PORT="${1:-5001}" + +if [ ! -x .venv/bin/python ]; then + echo "Creating local venv for Fava tooling..." + python -m venv --system-site-packages .venv + .venv/bin/pip install --quiet "beanquery" "simplejson<4" +fi + +exec .venv/bin/python -c "import sys; from fava.cli import main; \ +sys.argv=['fava','-p','${PORT}','ledger/lantern.beancount']; main()" From da0161963a68844a4d8014f8d756ea9fbae746f7 Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Thu, 30 Jul 2026 20:47:08 +0200 Subject: [PATCH 4/7] feat(deck): build the accounting pitch deck A full-screen slide presentation on the boilerplate: keyboard/hash navigation, progress chrome, and 12 slides walking from accounting principles through the mock project's ledger to the app concept and value props. Numbers are sourced from a single data module that mirrors the Beancount ledger. Visualizations: budgeted-allocation stacked bar, cash-over-time area chart, the $5k asset-swap, and a net-worth waterfall for the balance-sheet snapshot. Slides fill the viewport via vertical centering and a viewport-relative root font-size so the deck reads well when projected. Routes `/` to the deck (HomeView left in place as boilerplate scaffolding). Co-Authored-By: Claude Opus 4.8 (1M context) --- index.html | 2 +- src/features/deck/DeckView.vue | 95 ++++++++++++++ src/features/deck/components/AreaChart.vue | 110 +++++++++++++++++ src/features/deck/components/BarChart.vue | 38 ++++++ src/features/deck/components/SlideShell.vue | 37 ++++++ src/features/deck/components/StackedBar.vue | 33 +++++ src/features/deck/components/StatCard.vue | 25 ++++ src/features/deck/components/Waterfall.vue | 99 +++++++++++++++ src/features/deck/data.ts | 129 ++++++++++++++++++++ src/features/deck/slides/01-Title.vue | 41 +++++++ src/features/deck/slides/02-Problem.vue | 50 ++++++++ src/features/deck/slides/03-Principles.vue | 85 +++++++++++++ src/features/deck/slides/04-Project.vue | 69 +++++++++++ src/features/deck/slides/05-Investment.vue | 42 +++++++ src/features/deck/slides/06-Ledger.vue | 35 ++++++ src/features/deck/slides/07-Servers.vue | 57 +++++++++ src/features/deck/slides/08-Balances.vue | 53 ++++++++ src/features/deck/slides/09-Snapshot.vue | 29 +++++ src/features/deck/slides/10-App.vue | 56 +++++++++ src/features/deck/slides/11-Value.vue | 46 +++++++ src/features/deck/slides/12-Fava.vue | 68 +++++++++++ src/features/deck/slides/index.ts | 34 ++++++ src/features/deck/useDeck.ts | 64 ++++++++++ src/router/index.ts | 4 +- src/style.css | 6 + 25 files changed, 1304 insertions(+), 3 deletions(-) create mode 100644 src/features/deck/DeckView.vue create mode 100644 src/features/deck/components/AreaChart.vue create mode 100644 src/features/deck/components/BarChart.vue create mode 100644 src/features/deck/components/SlideShell.vue create mode 100644 src/features/deck/components/StackedBar.vue create mode 100644 src/features/deck/components/StatCard.vue create mode 100644 src/features/deck/components/Waterfall.vue create mode 100644 src/features/deck/data.ts create mode 100644 src/features/deck/slides/01-Title.vue create mode 100644 src/features/deck/slides/02-Problem.vue create mode 100644 src/features/deck/slides/03-Principles.vue create mode 100644 src/features/deck/slides/04-Project.vue create mode 100644 src/features/deck/slides/05-Investment.vue create mode 100644 src/features/deck/slides/06-Ledger.vue create mode 100644 src/features/deck/slides/07-Servers.vue create mode 100644 src/features/deck/slides/08-Balances.vue create mode 100644 src/features/deck/slides/09-Snapshot.vue create mode 100644 src/features/deck/slides/10-App.vue create mode 100644 src/features/deck/slides/11-Value.vue create mode 100644 src/features/deck/slides/12-Fava.vue create mode 100644 src/features/deck/slides/index.ts create mode 100644 src/features/deck/useDeck.ts diff --git a/index.html b/index.html index edd4762..cd41067 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - Boilerplate Website + Accounting for the Collective — Project Lantern
diff --git a/src/features/deck/DeckView.vue b/src/features/deck/DeckView.vue new file mode 100644 index 0000000..71419f5 --- /dev/null +++ b/src/features/deck/DeckView.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/src/features/deck/components/AreaChart.vue b/src/features/deck/components/AreaChart.vue new file mode 100644 index 0000000..3b6e840 --- /dev/null +++ b/src/features/deck/components/AreaChart.vue @@ -0,0 +1,110 @@ + + + diff --git a/src/features/deck/components/BarChart.vue b/src/features/deck/components/BarChart.vue new file mode 100644 index 0000000..e3c2fdf --- /dev/null +++ b/src/features/deck/components/BarChart.vue @@ -0,0 +1,38 @@ + + + diff --git a/src/features/deck/components/SlideShell.vue b/src/features/deck/components/SlideShell.vue new file mode 100644 index 0000000..7cadf42 --- /dev/null +++ b/src/features/deck/components/SlideShell.vue @@ -0,0 +1,37 @@ + + + diff --git a/src/features/deck/components/StackedBar.vue b/src/features/deck/components/StackedBar.vue new file mode 100644 index 0000000..e96f366 --- /dev/null +++ b/src/features/deck/components/StackedBar.vue @@ -0,0 +1,33 @@ + + + diff --git a/src/features/deck/components/StatCard.vue b/src/features/deck/components/StatCard.vue new file mode 100644 index 0000000..9f92663 --- /dev/null +++ b/src/features/deck/components/StatCard.vue @@ -0,0 +1,25 @@ + + + diff --git a/src/features/deck/components/Waterfall.vue b/src/features/deck/components/Waterfall.vue new file mode 100644 index 0000000..f952abc --- /dev/null +++ b/src/features/deck/components/Waterfall.vue @@ -0,0 +1,99 @@ + + + diff --git a/src/features/deck/data.ts b/src/features/deck/data.ts new file mode 100644 index 0000000..5d51af2 --- /dev/null +++ b/src/features/deck/data.ts @@ -0,0 +1,129 @@ +// --------------------------------------------------------------------------- +// Canonical figures for Project Lantern. +// These mirror ledger/lantern.beancount exactly so the deck and Fava agree. +// (Verified against `bean-check` + Fava's own totals on the ledger — see README.) +// --------------------------------------------------------------------------- + +export const money = (n: number) => + n.toLocaleString('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }) + +export const money2 = (n: number) => + n.toLocaleString('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 2 }) + +// The plan the seed round was raised against (what the deck calls "the budget"). +export const SEED = 100_000 + +export const budget = [ + { label: 'Salaries', amount: 50_000, color: 'sky' }, + { label: 'Hardware & Materials', amount: 30_000, color: 'amber' }, + { label: 'Operations', amount: 12_000, color: 'violet' }, + { label: 'Contingency', amount: 8_000, color: 'slate' }, +] as const + +// Cash on hand at the end of each month (Assets:Bank + Assets:Cash), from the +// ledger. Used for the "books over time" area chart. +export const cashByMonth = [ + { month: 'Jan', cash: 92_850 }, + { month: 'Feb', cash: 85_700 }, + { month: 'Mar', cash: 70_550 }, // card paid off + $5k servers capitalized out of cash + { month: 'Apr', cash: 58_835 }, + { month: 'May', cash: 45_685 }, + { month: 'Jun', cash: 42_535 }, // first $4k revenue lands, burn nearly flat + { month: 'Jul', cash: 43_885 }, // revenue > spend — the curve turns up +] as const + +// Expenses to date, by category (cash + the one non-cash equity grant). +export const expenses = [ + { label: 'Salaries', amount: 36_000, color: 'sky' }, + { label: 'Materials', amount: 13_500, color: 'amber' }, + { label: 'Operations', amount: 8_050, color: 'violet' }, + { label: 'Contributed (equity)', amount: 3_000, color: 'emerald' }, + { label: 'Fuel', amount: 65, color: 'rose' }, +] as const + +// Balance-sheet snapshot as of 2026-07-30, straight from the ledger. +export const snapshot = { + assets: { + total: 48_885, + rows: [ + { label: 'Bank — Checking', amount: 42_885 }, + { label: 'Cash — Petty cash float', amount: 1_000 }, + { label: 'Equipment — Servers', amount: 5_000 }, + ], + }, + liabilities: { + total: 0, + rows: [{ label: 'Outstanding reimbursements', amount: 0 }], + }, + equity: { + total: 48_885, + rows: [ + { label: 'Investor capital (seed)', amount: 100_000 }, + { label: 'Contributor equity (Bob)', amount: 3_000 }, + { label: 'Retained earnings (to date)', amount: -54_115 }, + ], + }, + revenueToDate: 6_500, + expensesToDate: 60_615, +} + +// People with a balance against the collective — the two shapes it can take. +export const balances = [ + { + name: 'Alice', + kind: 'liability' as const, + amount: 65, + story: 'Fronted her own cash for fuel in the org vehicle.', + meaning: 'The collective OWES Alice. A debt, cleared when she is paid back.', + }, + { + name: 'Bob', + kind: 'equity' as const, + amount: 3_000, + story: 'Contributed engineering work, took equity instead of cash.', + meaning: 'Bob OWNS a slice — a claim on future profit, not a debt to repay.', + }, + { + name: 'Atitlán Ventures', + kind: 'equity' as const, + amount: 100_000, + story: 'Wrote the seed cheque that started the project.', + meaning: 'The investor holds the largest claim on the upside.', + }, +] + +export const team = [ + { name: 'Pat', role: 'Software' }, + { name: 'Rayan', role: 'Accountant' }, +] + +// Tailwind class fragments keyed by our palette names (kept explicit so the +// JIT compiler always sees the full class string). +export const palette: Record = { + sky: { bar: 'bg-sky-500', text: 'text-sky-400', dot: 'bg-sky-500', soft: 'bg-sky-500/15' }, + amber: { + bar: 'bg-amber-500', + text: 'text-amber-400', + dot: 'bg-amber-500', + soft: 'bg-amber-500/15', + }, + violet: { + bar: 'bg-violet-500', + text: 'text-violet-400', + dot: 'bg-violet-500', + soft: 'bg-violet-500/15', + }, + emerald: { + bar: 'bg-emerald-500', + text: 'text-emerald-400', + dot: 'bg-emerald-500', + soft: 'bg-emerald-500/15', + }, + rose: { bar: 'bg-rose-500', text: 'text-rose-400', dot: 'bg-rose-500', soft: 'bg-rose-500/15' }, + slate: { + bar: 'bg-slate-500', + text: 'text-slate-400', + dot: 'bg-slate-500', + soft: 'bg-slate-500/15', + }, +} diff --git a/src/features/deck/slides/01-Title.vue b/src/features/deck/slides/01-Title.vue new file mode 100644 index 0000000..0f3a0e7 --- /dev/null +++ b/src/features/deck/slides/01-Title.vue @@ -0,0 +1,41 @@ + + + diff --git a/src/features/deck/slides/02-Problem.vue b/src/features/deck/slides/02-Problem.vue new file mode 100644 index 0000000..2034f18 --- /dev/null +++ b/src/features/deck/slides/02-Problem.vue @@ -0,0 +1,50 @@ + + + diff --git a/src/features/deck/slides/03-Principles.vue b/src/features/deck/slides/03-Principles.vue new file mode 100644 index 0000000..01dac2d --- /dev/null +++ b/src/features/deck/slides/03-Principles.vue @@ -0,0 +1,85 @@ + + + diff --git a/src/features/deck/slides/04-Project.vue b/src/features/deck/slides/04-Project.vue new file mode 100644 index 0000000..245aebc --- /dev/null +++ b/src/features/deck/slides/04-Project.vue @@ -0,0 +1,69 @@ + + + diff --git a/src/features/deck/slides/05-Investment.vue b/src/features/deck/slides/05-Investment.vue new file mode 100644 index 0000000..8c03f1a --- /dev/null +++ b/src/features/deck/slides/05-Investment.vue @@ -0,0 +1,42 @@ + + + diff --git a/src/features/deck/slides/06-Ledger.vue b/src/features/deck/slides/06-Ledger.vue new file mode 100644 index 0000000..310c78e --- /dev/null +++ b/src/features/deck/slides/06-Ledger.vue @@ -0,0 +1,35 @@ + + + diff --git a/src/features/deck/slides/07-Servers.vue b/src/features/deck/slides/07-Servers.vue new file mode 100644 index 0000000..82a6cfd --- /dev/null +++ b/src/features/deck/slides/07-Servers.vue @@ -0,0 +1,57 @@ + + + diff --git a/src/features/deck/slides/08-Balances.vue b/src/features/deck/slides/08-Balances.vue new file mode 100644 index 0000000..336d7d7 --- /dev/null +++ b/src/features/deck/slides/08-Balances.vue @@ -0,0 +1,53 @@ + + + diff --git a/src/features/deck/slides/09-Snapshot.vue b/src/features/deck/slides/09-Snapshot.vue new file mode 100644 index 0000000..83ced83 --- /dev/null +++ b/src/features/deck/slides/09-Snapshot.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/features/deck/slides/10-App.vue b/src/features/deck/slides/10-App.vue new file mode 100644 index 0000000..87a3ae5 --- /dev/null +++ b/src/features/deck/slides/10-App.vue @@ -0,0 +1,56 @@ + + + diff --git a/src/features/deck/slides/11-Value.vue b/src/features/deck/slides/11-Value.vue new file mode 100644 index 0000000..75298d6 --- /dev/null +++ b/src/features/deck/slides/11-Value.vue @@ -0,0 +1,46 @@ + + + diff --git a/src/features/deck/slides/12-Fava.vue b/src/features/deck/slides/12-Fava.vue new file mode 100644 index 0000000..1e313df --- /dev/null +++ b/src/features/deck/slides/12-Fava.vue @@ -0,0 +1,68 @@ + + + diff --git a/src/features/deck/slides/index.ts b/src/features/deck/slides/index.ts new file mode 100644 index 0000000..def6aab --- /dev/null +++ b/src/features/deck/slides/index.ts @@ -0,0 +1,34 @@ +import type { Component } from 'vue' + +import TitleSlide from './01-Title.vue' +import ProblemSlide from './02-Problem.vue' +import PrinciplesSlide from './03-Principles.vue' +import ProjectSlide from './04-Project.vue' +import InvestmentSlide from './05-Investment.vue' +import LedgerSlide from './06-Ledger.vue' +import ServersSlide from './07-Servers.vue' +import BalancesSlide from './08-Balances.vue' +import SnapshotSlide from './09-Snapshot.vue' +import AppSlide from './10-App.vue' +import ValueSlide from './11-Value.vue' +import FavaSlide from './12-Fava.vue' + +export interface Slide { + title: string + component: Component +} + +export const slides: Slide[] = [ + { title: 'Accounting for the Collective', component: TitleSlide }, + { title: 'The problem', component: ProblemSlide }, + { title: 'Accounting in 90 seconds', component: PrinciplesSlide }, + { title: 'Meet Project Lantern', component: ProjectSlide }, + { title: 'The money arrives', component: InvestmentSlide }, + { title: 'The books over time', component: LedgerSlide }, + { title: 'The $5,000 servers', component: ServersSlide }, + { title: 'Everyone has a balance', component: BalancesSlide }, + { title: 'Where we stand today', component: SnapshotSlide }, + { title: 'The application', component: AppSlide }, + { title: 'Why it matters', component: ValueSlide }, + { title: 'This is real, not a mockup', component: FavaSlide }, +] diff --git a/src/features/deck/useDeck.ts b/src/features/deck/useDeck.ts new file mode 100644 index 0000000..3f04345 --- /dev/null +++ b/src/features/deck/useDeck.ts @@ -0,0 +1,64 @@ +import { onMounted, onUnmounted, ref } from 'vue' + +// Small, self-contained deck controller: current slide index, next/prev, and +// keyboard + URL-hash sync so any slide is directly linkable (#3). +export function useDeck(total: number) { + const index = ref(clampFromHash(total)) + + function clamp(n: number) { + return Math.max(0, Math.min(total - 1, n)) + } + + function go(n: number) { + index.value = clamp(n) + window.location.hash = String(index.value + 1) + } + + const next = () => go(index.value + 1) + const prev = () => go(index.value - 1) + + function onKey(e: KeyboardEvent) { + switch (e.key) { + case 'ArrowRight': + case 'PageDown': + case ' ': + e.preventDefault() + next() + break + case 'ArrowLeft': + case 'PageUp': + e.preventDefault() + prev() + break + case 'Home': + e.preventDefault() + go(0) + break + case 'End': + e.preventDefault() + go(total - 1) + break + } + } + + function onHashChange() { + index.value = clampFromHash(total) + } + + onMounted(() => { + window.addEventListener('keydown', onKey) + window.addEventListener('hashchange', onHashChange) + }) + onUnmounted(() => { + window.removeEventListener('keydown', onKey) + window.removeEventListener('hashchange', onHashChange) + }) + + return { index, next, prev, go } +} + +function clampFromHash(total: number) { + const n = parseInt(window.location.hash.replace('#', ''), 10) + if (Number.isNaN(n)) return 0 + return Math.max(0, Math.min(total - 1, n - 1)) +} diff --git a/src/router/index.ts b/src/router/index.ts index 4f315f4..ad0397e 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -3,8 +3,8 @@ import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router' const routes: RouteRecordRaw[] = [ { path: '/', - name: 'home', - component: () => import('@/views/HomeView.vue'), + name: 'deck', + component: () => import('@/features/deck/DeckView.vue'), }, ] diff --git a/src/style.css b/src/style.css index 3ebb69e..4c32d12 100644 --- a/src/style.css +++ b/src/style.css @@ -80,4 +80,10 @@ body { @apply bg-background text-foreground; } + /* Scale the whole deck with the viewport. Every Tailwind rem-based size + (text, padding, gaps, max-width) is relative to this root font-size, so + bumping it enlarges the presentation uniformly to fill large screens. */ + html { + font-size: clamp(16px, 0.85vw + 6px, 23px); + } } From b7d8a94e977b9a95532e21df1c3b38badd35ee09 Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Thu, 30 Jul 2026 20:47:08 +0200 Subject: [PATCH 5/7] docs: rewrite README for the presentation Document the two deliverables (deck + ledger), how to run each, and the headline figures. Original boilerplate stack notes preserved in README.boilerplate.md. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.boilerplate.md | 48 ++++++++++++++++ README.md | 129 +++++++++++++++++++++++------------------- 2 files changed, 118 insertions(+), 59 deletions(-) create mode 100644 README.boilerplate.md diff --git a/README.boilerplate.md b/README.boilerplate.md new file mode 100644 index 0000000..0f97e03 --- /dev/null +++ b/README.boilerplate.md @@ -0,0 +1,48 @@ +# boilerplate-website + +Opinionated Vue 3 starter — the base every new aiolabs website forks from. +(This project — the accounting pitch deck — was forked from it; see the main +[`README.md`](./README.md).) + +## Stack + +- **Vue 3.5** + **Vite 8** + **TypeScript 6** +- **shadcn-vue** (via `reka-ui` + `class-variance-authority` + `tailwind-merge`) — components.json pre-wired, run `pnpm dlx shadcn-vue@latest add ` to copy components in +- **Tailwind CSS 4** (via `@tailwindcss/vite` — no `tailwind.config.js`) +- **Pinia 3** — sample `useCounterStore` in `src/stores/` +- **Vue Router 5** — file in `src/router/index.ts`, lazy-loaded views in `src/views/` +- **Vue I18n 11** — `src/i18n/locales/{en,es}.json` +- **vee-validate 4** + **zod 3** — form validation primitives (zod pinned to ^3 until `@vee-validate/zod` ships a v4-compatible resolver) +- **@lucide/vue** — icon set (`lucide-vue-next` is deprecated upstream) +- **ESLint 10** (flat config) + **Prettier 3** + +## Quick start + +```sh +pnpm install +pnpm dev # vite dev server +pnpm build # type-check + production build +pnpm preview # serve dist/ +pnpm lint +pnpm format +``` + +## Optional features + +Both nostr and LNbits live as **documentation-only** folders under +`src/features/`. The deps aren't installed by default — bundle stays small for +sites that don't need them. Each folder's README walks you through enabling. + +- **`src/features/nostr/`** — connect to relays, sign/publish events, contact forms that DM the site owner's npub +- **`src/features/lnbits/`** — create invoices, accept Lightning payments via an LNbits instance + +## Pulling boilerplate dep refreshes into a forked site + +```sh +git remote add boilerplate forgejo@git.atitlan.io:aiolabs/boilerplate-website.git +git fetch boilerplate +git merge boilerplate/main +``` + +When a site diverges enough to no longer benefit from these merges, drop the +remote — it becomes its own thing. diff --git a/README.md b/README.md index 977732b..a9e012c 100644 --- a/README.md +++ b/README.md @@ -1,74 +1,85 @@ -# boilerplate-website +# Accounting for the Collective — pitch deck -Opinionated Vue 3 starter — the base every new aiolabs website forks from. +A slide presentation that pitches an incubator collective on adopting real +accounting practices, plus the **Beancount ledger** that backs every number in +it. Built on the aiolabs `boilerplate-website` (Vue 3 + Vite + Tailwind 4). -## Stack +Presented by the accounting team: **Pat** (software) and **Rayan** (accountant). -- **Vue 3.5** + **Vite 8** + **TypeScript 6** -- **shadcn-vue** (via `reka-ui` + `class-variance-authority` + `tailwind-merge`) — components.json pre-wired, run `pnpm dlx shadcn-vue@latest add ` to copy components in -- **Tailwind CSS 4** (via `@tailwindcss/vite` — no `tailwind.config.js`) -- **Pinia 3** — sample `useCounterStore` in `src/stores/` -- **Vue Router 5** — file in `src/router/index.ts`, lazy-loaded views in `src/views/` -- **Vue I18n 11** — `src/i18n/locales/{en,es}.json` -- **vee-validate 4** + **zod 3** — form validation primitives (zod pinned to ^3 until `@vee-validate/zod` ships a v4-compatible resolver) -- **@lucide/vue** — icon set (`lucide-vue-next` is deprecated upstream) -- **ESLint 10** (flat config) + **Prettier 3** +## Two deliverables, one story -## Quick start +Both tell the story of **Project Lantern** — a mock hardware project inside the +collective — over its first seven months (Jan–Jul 2026). + +1. **The deck** (`src/features/deck/`) — a full-screen slide presentation with + simplified visualizations: budget allocation, cash-over-time, the + double-entry behind a $5,000 server purchase, who holds a balance with the + collective, and a live balance sheet. +2. **The ledger** (`ledger/lantern.beancount`) — the same story as a real + plain-text ledger you open in **Fava** to show the actual reporting software + behind the pitch. The deck's figures are pulled straight from it (see + `src/features/deck/data.ts`), so the two always agree. + +## Run the deck ```sh pnpm install -pnpm dev # vite dev server -pnpm build # type-check + production build -pnpm preview # serve dist/ -pnpm lint -pnpm format +pnpm dev # → http://localhost:5173 +pnpm build # type-check + production build ``` +Navigate with **← / →** (or space), **Home/End** to jump, click the dots, or +deep-link a slide with a hash (e.g. `/#7`). + +## Run the reporting (Fava) + +The system Fava package is missing its `beanquery` dependency, so a helper +script spins up a local venv (reusing system packages) the first time: + +```sh +./ledger/run-fava.sh # → http://localhost:5001 +# or choose a port: ./ledger/run-fava.sh 5005 +``` + +Validate the ledger directly with Beancount: + +```sh +bean-check ledger/lantern.beancount # should print nothing = valid +``` + +In Fava, the interesting views are **Balance Sheet**, **Income Statement**, +**Budget** (the plan the seed round was raised against), the **Journal**, and +**Events** (project milestones). + +## The numbers (as of 2026-07-30) + +| | | +|---|---:| +| Seed round raised | $100,000 | +| Net worth today (total assets) | $48,885 | +| — Bank + petty cash | $43,885 | +| — Equipment (servers, capitalized) | $5,000 | +| Revenue to date (pre-orders) | $6,500 | +| Expenses to date | $60,615 | + +Assets ($48,885) = Liabilities ($0) + Equity ($48,885). The books balance — +verified against Fava and `bean-check`. + ## Layout ``` -src/ -├─ App.vue # router-view shell -├─ main.ts # plugin wiring -├─ style.css # tailwind + shadcn-vue CSS variables -├─ lib/utils.ts # cn() — shadcn-vue's class merger -├─ router/index.ts -├─ stores/counter.ts # example pinia store -├─ i18n/ -│ ├─ index.ts -│ └─ locales/{en,es}.json -├─ views/HomeView.vue # proof-of-wiring page (i18n + pinia + tailwind) -└─ features/ - ├─ nostr/README.md # opt-in: nostr-tools wiring (see file) - └─ lnbits/README.md # opt-in: LNbits payments wiring (see file) +ledger/ +├─ lantern.beancount # the mock project ledger (open in Fava) +└─ run-fava.sh # launches Fava in a local venv +src/features/deck/ +├─ DeckView.vue # slide shell: nav, progress, keyboard/hash routing +├─ useDeck.ts # navigation composable +├─ data.ts # canonical figures (mirror the ledger) +├─ components/ # SlideShell, BarChart, StackedBar, AreaChart, StatCard +└─ slides/ # 01-Title … 12-Fava ``` -## Optional features +--- -Both nostr and LNbits live as **documentation-only** folders. The deps -aren't installed by default — bundle stays small for sites that don't -need them. Each folder's README walks you through enabling. - -- **`src/features/nostr/`** — connect to relays, sign/publish events, contact forms that DM the site owner's npub -- **`src/features/lnbits/`** — create invoices, accept Lightning payments via an LNbits instance - -## Versioning strategy - -The boilerplate's `main` branch is **the "tools wired, no content" baseline**. New site = clone (or fork on Forgejo) → start adding content immediately. - -To pull dep refreshes from the boilerplate into an existing site: - -```sh -git remote add boilerplate forgejo@git.atitlan.io:aiolabs/boilerplate-website.git -git fetch boilerplate -git merge boilerplate/main -``` - -When a site diverges enough to no longer benefit from these merges, drop the remote — it becomes its own thing. - -## Keeping deps current - -- The boilerplate gets dep bumps via PRs (Renovate / Dependabot configurable; otherwise periodic `pnpm update --latest` + manual review). -- The Vue ecosystem (vue, pinia, router, i18n, vee-validate) versions in lockstep — when one majors, the others usually follow within weeks. -- Tailwind 4 + shadcn-vue + reka-ui is the current modern combo. shadcn-vue uses tw-animate-css (not the deprecated tailwindcss-animate plugin). +Scaffolding stack notes from the boilerplate are preserved in +[`README.boilerplate.md`](./README.boilerplate.md). From a6f6b5bb15487b7f9b3022751fc920774f057a40 Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Thu, 30 Jul 2026 20:54:30 +0200 Subject: [PATCH 6/7] feat(deck): add spend-breakdown slide and refine balances MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a "Where the money went" slide after the cash-over-time chart, using a bar chart of expenses by category (salaries, materials, operations, contributed equity, fuel) sourced from the ledger — it answers the obvious "where did the burn go?" question the cash curve leaves open. Rework the balances slide into two labelled zones (Liabilities the collective owes vs Equity claims on it) with a divider and larger type, rather than a proportional chart that would render a $65 balance invisible next to $100k. Also fix BarChart value labels: an absent Vue Boolean prop casts to false, so the old `showValues !== false` guard always hid the amounts — inverted to `hideValues`. Rename Waterfall -> WaterfallChart for the multi-word lint rule and enlarge chart type for projector readability. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/features/deck/components/BarChart.vue | 18 ++-- .../{Waterfall.vue => WaterfallChart.vue} | 0 src/features/deck/slides/07-Spend.vue | 37 +++++++++ src/features/deck/slides/08-Balances.vue | 53 ------------ .../slides/{07-Servers.vue => 08-Servers.vue} | 0 src/features/deck/slides/09-Balances.vue | 82 +++++++++++++++++++ .../{09-Snapshot.vue => 10-Snapshot.vue} | 4 +- .../deck/slides/{10-App.vue => 11-App.vue} | 0 .../slides/{11-Value.vue => 12-Value.vue} | 0 .../deck/slides/{12-Fava.vue => 13-Fava.vue} | 0 src/features/deck/slides/index.ts | 14 ++-- 11 files changed, 139 insertions(+), 69 deletions(-) rename src/features/deck/components/{Waterfall.vue => WaterfallChart.vue} (100%) create mode 100644 src/features/deck/slides/07-Spend.vue delete mode 100644 src/features/deck/slides/08-Balances.vue rename src/features/deck/slides/{07-Servers.vue => 08-Servers.vue} (100%) create mode 100644 src/features/deck/slides/09-Balances.vue rename src/features/deck/slides/{09-Snapshot.vue => 10-Snapshot.vue} (90%) rename src/features/deck/slides/{10-App.vue => 11-App.vue} (100%) rename src/features/deck/slides/{11-Value.vue => 12-Value.vue} (100%) rename src/features/deck/slides/{12-Fava.vue => 13-Fava.vue} (100%) diff --git a/src/features/deck/components/BarChart.vue b/src/features/deck/components/BarChart.vue index e3c2fdf..7fa1e90 100644 --- a/src/features/deck/components/BarChart.vue +++ b/src/features/deck/components/BarChart.vue @@ -2,24 +2,26 @@ import { computed } from 'vue' import { palette, money } from '../data' +// Note: Vue casts an absent Boolean prop to `false`, so a "showValues" default +// of true isn't possible without a runtime default — invert it to hideValues. const props = defineProps<{ items: readonly { label: string; amount: number; color: string }[] - showValues?: boolean + hideValues?: boolean }>() const max = computed(() => Math.max(...props.items.map((i) => i.amount), 1))