diff --git a/.gitignore b/.gitignore index 5fb81ec..ad5f2f7 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,4 @@ dist-ssr *.log pnpm-debug.log* -# python venv used to run Fava (see ledger/run-fava.sh) -.venv/ - .claude/ diff --git a/Accounting-Presentation.md b/Accounting-Presentation.md deleted file mode 100644 index 1033ce5..0000000 --- a/Accounting-Presentation.md +++ /dev/null @@ -1,24 +0,0 @@ -# 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 Ryan (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 diff --git a/README.boilerplate.md b/README.boilerplate.md deleted file mode 100644 index 0f97e03..0000000 --- a/README.boilerplate.md +++ /dev/null @@ -1,48 +0,0 @@ -# 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 98afc9c..977732b 100644 --- a/README.md +++ b/README.md @@ -1,85 +1,74 @@ -# Accounting for the Collective — pitch deck +# boilerplate-website -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). +Opinionated Vue 3 starter — the base every new aiolabs website forks from. -Presented by the accounting team: **Pat** (software) and **Ryan** (accountant). +## Stack -## Two deliverables, one story +- **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** -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 +## Quick start ```sh pnpm install -pnpm dev # → http://localhost:5173 -pnpm build # type-check + production build +pnpm dev # vite dev server +pnpm build # type-check + production build +pnpm preview # serve dist/ +pnpm lint +pnpm format ``` -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 ``` -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 +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) ``` ---- +## Optional features -Scaffolding stack notes from the boilerplate are preserved in -[`README.boilerplate.md`](./README.boilerplate.md). +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). diff --git a/index.html b/index.html index cd41067..381d0bd 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - Accounting for the Collective — Project Lantern + Archipelago — Investment Prospectus
diff --git a/ledger/lantern.beancount b/ledger/lantern.beancount deleted file mode 100644 index 0e940be..0000000 --- a/ledger/lantern.beancount +++ /dev/null @@ -1,267 +0,0 @@ -;; ============================================================================ -;; 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:Ryan 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:Ryan "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:Ryan 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:Ryan 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:Ryan 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:Ryan 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:Ryan 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:Ryan 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 deleted file mode 100755 index 0fa1b76..0000000 --- a/ledger/run-fava.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/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()" diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ae1acad..dbc8714 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,5 @@ +# Approve dependencies allowed to run install scripts (pnpm 11). +# vue-demi's postinstall selects its Vue 2/3 entrypoint; without this, +# `pnpm dev` / `pnpm build` fail the pre-run ignored-builds check. allowBuilds: vue-demi: true diff --git a/src/deck/Deck.vue b/src/deck/Deck.vue new file mode 100644 index 0000000..bd36b45 --- /dev/null +++ b/src/deck/Deck.vue @@ -0,0 +1,149 @@ + + + + + diff --git a/src/deck/components/EntityDetail.vue b/src/deck/components/EntityDetail.vue new file mode 100644 index 0000000..746afc4 --- /dev/null +++ b/src/deck/components/EntityDetail.vue @@ -0,0 +1,77 @@ + + + diff --git a/src/deck/components/Icon.vue b/src/deck/components/Icon.vue new file mode 100644 index 0000000..e46ad45 --- /dev/null +++ b/src/deck/components/Icon.vue @@ -0,0 +1,78 @@ + + + diff --git a/src/deck/components/Lightbox.vue b/src/deck/components/Lightbox.vue new file mode 100644 index 0000000..ce78a9e --- /dev/null +++ b/src/deck/components/Lightbox.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/src/deck/components/MyceliumNetwork.vue b/src/deck/components/MyceliumNetwork.vue new file mode 100644 index 0000000..b78771d --- /dev/null +++ b/src/deck/components/MyceliumNetwork.vue @@ -0,0 +1,408 @@ + + + + + diff --git a/src/deck/components/PlaceholderBadge.vue b/src/deck/components/PlaceholderBadge.vue new file mode 100644 index 0000000..49f1a42 --- /dev/null +++ b/src/deck/components/PlaceholderBadge.vue @@ -0,0 +1,13 @@ + + + diff --git a/src/deck/components/SlideFrame.vue b/src/deck/components/SlideFrame.vue new file mode 100644 index 0000000..5816b0d --- /dev/null +++ b/src/deck/components/SlideFrame.vue @@ -0,0 +1,39 @@ + + + diff --git a/src/deck/data.ts b/src/deck/data.ts new file mode 100644 index 0000000..24c3645 --- /dev/null +++ b/src/deck/data.ts @@ -0,0 +1,530 @@ +/** + * ARCHIPELAGO — pitch deck content & figures. + * + * Single source of truth for the deck — no slide hard-codes a number or name. + * + * The financials are REAL, from SolLunarSociety.md, and belong exclusively to + * the first node — SolLunar Society (Austin). Archipelago (Lab 484), Pop-up + * Festivals and Chateau du Faune are narrative/vision only and have no + * standalone numbers. P&L totals are recomputed from line items; the doc's + * own summary totals didn't reconcile (kept in `pnlDocSummary`). + * + * Archipelago is the shared backbone — open hardware + sovereign software (the + * "mycelium"). Three surface entities run on top of it: + * • SolLunar Society — city members' club, Austin (the funded first node) + * • Pop-up Festivals — travelling gatherings (e.g. Solarpunk Summit) + * • Chateau du Faune — regenerative farm center + * + * Currency: all figures are USD unless noted. + */ + +export const PLACEHOLDER = true + +export const brand = { + name: 'Archipelago', + short: 'Archipelago', + // a connected network of islands — one backbone, many places. + tagline: 'One network. Three ways to experience it.', + subtagline: + 'A sovereign network on the Archipelago backbone. The first node — SolLunar Society, a members’ club in Austin — is live, modeled and raising now; the festival and the farm are next.', + firstNode: 'SolLunar Society · Austin, TX', + contactEmail: 'invest@sollunar.example', // PLACEHOLDER + // Seam for later: an LNbits instance + the founder's nostr npub power the + // "contribute" tiers. Left null until wired (see src/features/{lnbits,nostr}). + lnbitsBaseUrl: null as string | null, + nostrNpub: null as string | null, +} + +/** Big-picture framing for the vision slide (from the SolLunar Society plan). */ +export const problem = { + headline: 'Transformation fades without the infrastructure to sustain it.', + points: [ + 'People leave transformational events inspired — then return to environments that don’t support the change.', + 'There’s no lasting home for sovereignty-minded community: dreamers, creatives, technologists, regenerative living.', + 'Peak experiences don’t compound without ongoing relationships, education, and a place to practice.', + ], + thesis: + 'SolLunar Society is the missing infrastructure — a community hub where the work begun at transformational events continues through membership, tenants, events and regenerative living.', +} + +export type Accent = 'pine' | 'indigo' | 'rose' | 'clay' + +export type Entity = { + id: string + name: string + role: string // short label, e.g. "City center" + kind: string + accent: Accent + layer: 'backbone' | 'surface' + example?: string // for the generic festival entity + blurb: string + traits: string[] // what makes THIS entity unique + spaces: string[] + revenue: string[] // how this entity earns + icon: string +} + +/** + * The entities. Archipelago is the backbone; the other three are the + * experiences that run on it. PLACEHOLDER names & details. + */ +export const entities: Entity[] = [ + { + id: 'sollunar', + name: 'SolLunar Society', + role: 'City center · the first node', + kind: 'Members’ club & community hub · Austin, TX', + accent: 'indigo', + layer: 'surface', + blurb: + 'The first node and the one we’re raising for: a members’ club in Austin where sovereignty-minded community — tenants, events, memberships and regenerative living — comes together year-round on the Archipelago stack.', + traits: [ + 'Live, financially-modeled first node (real P&L)', + 'Five founding tenants + a three-tier membership', + 'Bitcoin ATM, vending & the full member ecosystem on site', + ], + spaces: ['Retail space', 'Event space', 'Private lounge', 'Co-working', 'Yellow House hostel & data center'], + revenue: ['Memberships', 'Tenants', 'Events & venue buyouts'], + icon: 'Building2', + }, + { + id: 'festivals', + name: 'Pop-up Festivals', + role: 'Travelling gatherings', + kind: 'Travelling · ephemeral', + accent: 'rose', + layer: 'surface', + example: 'Solar Punk Summit', + blurb: + 'The travelling node: a repeatable festival format — like Solar Punk Summit — that deploys the entire stack anywhere for a few days, then packs down and moves on.', + traits: [ + 'Deploys the full stack from a mobile kit — anywhere', + 'Proves the network is portable, not tied to a building', + 'Turns every festival into a member-onboarding event', + ], + spaces: ['Main stage', 'Mobile exchange kiosk', 'Mesh & power rig', 'Camp & vendors', 'Workshop tents'], + revenue: ['Ticket sales', 'Vendor & bar revenue', 'Sponsorships'], + icon: 'Tent', + }, + { + id: 'chateau', + name: 'Chateau du Faune', + role: 'Farm center', + kind: 'Off-grid · regenerative farm', + accent: 'clay', + layer: 'surface', + blurb: + 'The rural node: an off-grid regenerative farm hosting retreats, artist & developer residencies, and the network’s living lab — home to the alpacas.', + traits: [ + 'Proves the stack runs off-grid, anywhere', + 'Premium revenue from retreats & residencies', + 'Sponsor-an-alpaca connects small backers to the land', + ], + spaces: ['Retreat lodging', 'Residency studios', 'Event barn', 'Alpaca paddocks', 'Regenerative gardens'], + revenue: ['Lodging & retreats', 'Residencies', 'Experiences & venue hire'], + icon: 'Trees', + }, + { + id: 'archipelago', + name: 'Archipelago', + role: 'Shared backbone', + kind: 'Open hardware & sovereign software', + accent: 'pine', + layer: 'backbone', + blurb: + 'The invisible foundation every entity runs on: open hardware and self-hosted software — the mycelium beneath the whole network. Build it once, and every place plugs in.', + traits: [ + 'Powers all three entities from one shared stack', + 'Open bill of materials — replicable anywhere', + 'Forkable and sovereign — no platform lock-in', + ], + spaces: ['Edge servers', 'Mesh routers', 'Exchange kiosks', 'NFC / membership', 'Self-hosted apps'], + revenue: ['Network & kiosk fees', 'Payment fees', 'Replication licensing'], + icon: 'Cpu', + }, +] + +export const entityById = (id: string) => entities.find((e) => e.id === id)! +/** The three experiences (everything except the backbone). */ +export const surfaceEntities = entities.filter((e) => e.layer === 'surface') + +/** + * The user ecosystem every location shares — the "full experience regardless + * of location" promise. This is what a member touches at any node. + */ +export const userEcosystem = [ + { + title: 'One membership card', + body: 'A single NFC card is your identity, access and wallet everywhere — the society, a festival, the farm.', + icon: 'CreditCard', + }, + { + title: 'Money exchange kiosk', + body: 'On-ramp cash to Bitcoin and back at any location. The same kiosk, everywhere.', + icon: 'Coins', + }, + { + title: 'Pay by Lightning', + body: 'Tap to pay for drinks, tickets, rooms and experiences — instant, low-fee settlement.', + icon: 'Zap', + }, + { + title: 'The Archipelago app', + body: 'Book spaces, buy tickets, find events and manage membership from one self-hosted app.', + icon: 'Sparkles', + }, + { + title: 'Portable identity', + body: 'Your nostr identity and social graph travel with you between every node and beyond.', + icon: 'KeyRound', + }, + { + title: 'Member perks', + body: 'Access, discounts and priority booking that follow you across the whole network.', + icon: 'HeartHandshake', + }, +] + +/** The Archipelago backbone / core stack (the infrastructure pillars). */ +export const tech = [ + { + title: 'Decentralized mesh network', + body: 'Community-owned routers and mesh kits blanket each location — connectivity no single provider can switch off.', + icon: 'Wifi', + }, + { + title: 'Open hardware', + body: 'Servers, routers, exchange kiosks and NFC hardware from a documented, replicable bill of materials.', + icon: 'Cpu', + }, + { + title: 'Sovereign software', + body: 'Self-hosted services on a stack we control — no platform lock-in, forkable by any future node.', + icon: 'Code', + }, + { + title: 'Bitcoin & Lightning', + body: 'Instant, low-fee settlement at kiosks and NFC points. Value stays in the network, not the middleman.', + icon: 'Zap', + }, + { + title: 'Nostr identity & comms', + body: 'Portable identity, messaging and social layer that travels with members across every node.', + icon: 'KeyRound', + }, + { + title: 'Replicable blueprint', + body: 'Every node ships its build docs. Proven once, the whole stack redeploys anywhere in the world.', + icon: 'Globe', + }, +] + +/* ───────────────────────────────────────────────────────────── + SolLunar Society — the first node's REAL business (from + SolLunarSociety.md). All figures below are SolLunar Society's + only; Archipelago (Lab 484), Festivals and Chateau have no + standalone numbers yet. Lab 484's economics appear here as + revenue lines INTO SolLunar (~$16k/mo) — don't double-count. + ───────────────────────────────────────────────────────────── */ + +/** Ownership & partnership of the SolLunar Society JV. */ +export type Partner = { + name: string + short?: string + equity: number // 0..1 + accent: Accent | 'amber' + role: string + responsibilities: string[] + note?: string +} +export const partnership = { + valuation: 4_000_000, + raiseAmount: 400_000, + equityOffered: 0.1, + partners: [ + { + name: 'SolLunar Punk Society', + short: 'SPS', + equity: 0.45, + accent: 'indigo', + role: 'Space & community', + responsibilities: [ + 'Curate & decorate the space', + 'Lease to tenants · insurance', + 'Outbound payments · SOPs', + 'Marketing & community management', + ], + }, + { + name: 'Lab 484', + equity: 0.45, + accent: 'pine', + role: 'Tech & operations', + responsibilities: [ + 'Operate the spaces & staff', + 'All IT / infrastructure (Archipelago)', + 'Inbound payments · bootcamps', + 'Tenant & investor relations', + ], + note: 'Builds & operates the Archipelago backbone — and is itself a tenant + event driver (~$16k/mo of the revenue below).', + }, + { + name: 'Investors', + equity: 0.1, + accent: 'amber', + role: 'Capital', + responsibilities: ['$400,000 for 10% equity'], + }, + ] as Partner[], +} + +/** The five founding tenants inside SolLunar Society. */ +export const tenants = [ + { + name: 'Lab 484', + icon: 'Cpu', + blurb: + 'Research lab & startup cluster building a “second internet” — Bitcoin, mesh networks, Nostr, e-cash and P2P. User-owned hardware, software and data.', + }, + { + name: 'Solarpunk Summit', + icon: 'Tent', + blurb: + 'Transformational platform & community builder — immersive experiences, educational programming and regenerative culture.', + }, + { + name: 'Noderunners Shop', + icon: 'Router', + blurb: + 'Retail for AI computers, mesh gear, Bitcoin nodes and repair. Open-source, secure laptops, phones and servers.', + }, + { + name: 'Divine Provisions', + icon: 'Coins', + blurb: + 'A food co-op for low-cost, organic, local groceries — offered as a member service at the building entrance.', + }, + { + name: 'Kitchen 484', + icon: 'Sparkles', + blurb: + 'Community food truck — organic, seed-oil-free, locally sourced. Runs even when disconnected from the internet.', + }, +] + +/** Membership tiers (customer product / recurring revenue). */ +export type Membership = { + name: string + price: number // monthly + target: number // member-count target + blurb: string + perks: string[] + featured?: boolean +} +export const memberships: Membership[] = [ + { + name: 'Solarpunk Society', + price: 50, + target: 150, + blurb: 'Events access.', + perks: ['Member events', 'Amenities, vending & Bitcoin ATM', 'Access to tenants'], + }, + { + name: 'Cypherpunk Commons', + price: 350, + target: 50, + featured: true, + blurb: 'Full access, all locations.', + perks: [ + 'Coworking with the Lab 484 team', + 'All locations · tech support', + 'Bitcoin ATM, vending, coffee & tea', + 'Full tenant & amenity access', + ], + }, + { + name: 'Lunarpunk Locals', + price: 150, + target: 100, + blurb: 'Coworking days.', + perks: ['Coworking days at SolLunar', 'Access to tenants', 'Amenities, vending & Bitcoin ATM'], + }, +] + +/** Comparable Austin memberships, for pricing context. */ +export const membershipComparables = [ + { name: 'Soho House', price: 350 }, + { name: 'Bond Collective', price: 300 }, + { name: 'Bathe', price: 200 }, + { name: 'Stardust Garage', price: 155 }, + { name: 'Submersive', price: 41 }, +] + +/** + * SolLunar Society monthly P&L. Line items are from the doc; category totals + * are RECOMPUTED from them (the doc's summary totals didn't reconcile — see + * `pnlDocSummary`). Flagged oddity: Lunarpunk Locals income ($1,500) doesn't + * match its "100 × $150" annotation. + */ +export type PnlItem = { label: string; amount: number; note?: string } +export type PnlGroup = { + id: string + title: string + icon: string + accent: Accent + kind: 'revenue' | 'expense' + items: PnlItem[] +} + +export const pnlGroups: PnlGroup[] = [ + { + id: 'memberships', + title: 'Memberships', + icon: 'CreditCard', + accent: 'indigo', + kind: 'revenue', + items: [ + { label: 'Cypherpunk Commons', amount: 17_500, note: '50 × $350' }, + { label: 'Solarpunk Society', amount: 7_500, note: '150 × $50' }, + { label: 'Lunarpunk Locals', amount: 1_500, note: '$150/mo · count TBD' }, + { label: 'Vending machine sales', amount: 5_000 }, + { label: 'Promo / B2B network sales', amount: 5_000 }, + ], + }, + { + id: 'events', + title: 'Events', + icon: 'PartyPopper', + accent: 'rose', + kind: 'revenue', + items: [ + { label: 'Lab 484 tech events', amount: 8_000 }, + { label: 'Lab 484 bootcamps', amount: 6_000 }, + { label: 'Venue buyouts', amount: 6_400, note: '$400/hr · 4hr min · 4×/mo' }, + { label: 'SolLunar Society events', amount: 1_500, note: '$50 weekend passes' }, + ], + }, + { + id: 'tenants', + title: 'Tenants', + icon: 'Building2', + accent: 'pine', + kind: 'revenue', + items: [ + { label: 'Solarpunk Summit', amount: 2_000 }, + { label: 'Lab 484', amount: 2_000 }, + { label: 'Divine Provisions', amount: 1_500 }, + { label: 'Noderunners Shop', amount: 1_500 }, + ], + }, + { + id: 'expenses', + title: 'Expenses', + icon: 'Coins', + accent: 'clay', + kind: 'expense', + items: [ + { label: 'Staff', amount: 22_500, note: '4 staff' }, + { label: 'Rent', amount: 6_500, note: '$4,500 + triple net' }, + { label: 'Overhead', amount: 3_000 }, + { label: 'Utilities', amount: 2_000, note: 'electric, water, trash' }, + ], + }, +] + +export const groupTotal = (g: PnlGroup) => g.items.reduce((s, i) => s + i.amount, 0) +const sumKind = (k: 'revenue' | 'expense') => + pnlGroups.filter((g) => g.kind === k).reduce((s, g) => s + groupTotal(g), 0) + +/** Recomputed monthly/yearly economics for SolLunar Society. */ +export const pnl = { + monthlyRevenue: sumKind('revenue'), + monthlyExpenses: sumKind('expense'), + monthlyProfit: sumKind('revenue') - sumKind('expense'), + yearlyProfit: (sumKind('revenue') - sumKind('expense')) * 12, +} +/** What the source doc's summary table stated (did NOT reconcile with line items). */ +export const pnlDocSummary = { revenue: 71_400, expenses: 38_500, profit: 32_900, yearlyProfit: 394_800 } + +/** The building & spaces. */ +export const buildings = [ + { + name: 'SolLunar Society', + rooms: ['Retail space', 'Event space', 'Private office', 'Private lounge'], + }, + { + name: 'Lab 484 · Yellow House 1', + rooms: ['Hostel room · $40/night', 'Healer room', 'Coworking', 'Meeting room', 'Outdoor'], + }, + { + name: 'Lab 484 · Yellow House 2', + rooms: ['VIP guest room', 'Office', 'Data center', 'Outdoor'], + }, +] + +/** The investor ask. */ +export const investorAsk = { + amount: 400_000, + equity: 0.1, + valuation: 4_000_000, + scenarios: [0.1, 0.25], // profit-share scenarios on the recomputed yearly profit +} + +/** Roadmap / replication story. SolLunar Society first, then the network. */ +export const roadmap = [ + { + phase: 'Phase 0', + title: 'Raise & open SolLunar', + when: 'Now', + body: 'Close the $400k round, sign the lease, fit out the space and onboard the founding tenants.', + }, + { + phase: 'Phase 1', + title: 'Operate & prove the model', + when: '~0–12 mo', + body: 'Run memberships, events and tenants in Austin; validate the P&L publicly on the Archipelago stack.', + }, + { + phase: 'Phase 2', + title: 'Add the festival', + when: '~12–18 mo', + body: 'Deploy the same stack to a travelling Solarpunk Summit — proving the network is portable, not tied to a building.', + }, + { + phase: 'Phase 3', + title: 'Add the farm & replicate', + when: '18 mo+', + body: 'Open Chateau du Faune and publish the replication kit — new nodes plug into the same backbone anywhere.', + }, +] + +/** Formatting helpers used across slides. Millions render as $X.XXMM. */ +export const fmtUsd = (n: number) => { + if (n >= 1_000_000) + return `$${(n / 1_000_000).toLocaleString('en-US', { maximumFractionDigits: 2 })}MM` + if (n >= 1000) + return `$${(n / 1000).toLocaleString('en-US', { maximumFractionDigits: n % 1000 === 0 ? 0 : 1 })}k` + return `$${n.toLocaleString('en-US')}` +} + +export const fmtUsdFull = (n: number) => `$${n.toLocaleString('en-US')}` + +/** Tailwind class helpers keyed by entity accent — keeps slides declarative. */ +export const accentText: Record = { + pine: 'text-pine-300', + indigo: 'text-indigo-300', + rose: 'text-rose-300', + clay: 'text-clay-400', +} +export const accentDot: Record = { + pine: 'bg-pine-400', + indigo: 'bg-indigo-400', + rose: 'bg-rose-400', + clay: 'bg-clay-400', +} +export const accentRing: Record = { + pine: 'border-pine-500/30 bg-pine-500/5', + indigo: 'border-indigo-500/30 bg-indigo-500/5', + rose: 'border-rose-500/30 bg-rose-500/5', + clay: 'border-clay-400/30 bg-clay-400/5', +} diff --git a/src/deck/gallery.ts b/src/deck/gallery.ts new file mode 100644 index 0000000..ba34758 --- /dev/null +++ b/src/deck/gallery.ts @@ -0,0 +1,82 @@ +/** + * Gallery auto-population. + * + * Drop image files into these folders and they appear in the deck gallery + * automatically — no code changes, no manifest to edit: + * + * src/deck/gallery/city/ → City (SolLunar Punk Society) + * src/deck/gallery/farm/ → Farm (Chateau du Faune) + * src/deck/gallery/festival/ → Festival (Pop-up Festivals) + * + * Supported: jpg jpeg png webp avif gif svg. Images show sorted by filename, + * so prefix with 01-, 02-, … to control order. The seeded *-placeholder.svg + * files are just demo art — delete them once you add real photos. + * + * Under the hood this uses Vite's import.meta.glob, which must take a literal + * pattern (no variables) — hence one call per folder. + */ +import { entityById, type Accent } from './data' + +type GlobMap = Record + +// NB: import.meta.glob is statically analysed by Vite — its options must be an +// inline object literal (not a shared const), so the object is repeated below. +const city = import.meta.glob('./gallery/city/*.{jpg,jpeg,png,webp,avif,gif,svg}', { + eager: true, + query: '?url', + import: 'default', +}) as GlobMap +const farm = import.meta.glob('./gallery/farm/*.{jpg,jpeg,png,webp,avif,gif,svg}', { + eager: true, + query: '?url', + import: 'default', +}) as GlobMap +const festival = import.meta.glob('./gallery/festival/*.{jpg,jpeg,png,webp,avif,gif,svg}', { + eager: true, + query: '?url', + import: 'default', +}) as GlobMap + +export type GalleryImage = { src: string; name: string } + +function toImages(map: GlobMap): GalleryImage[] { + return Object.entries(map) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([path, src]) => ({ src, name: path.split('/').pop()!.replace(/\.[^.]+$/, '') })) +} + +export type GallerySection = { + id: 'city' | 'farm' | 'festival' + label: string + entityName: string + accent: Accent + folder: string + images: GalleryImage[] +} + +export const gallerySections: GallerySection[] = [ + { + id: 'city', + label: 'City', + entityName: entityById('sollunar').name, + accent: 'indigo', + folder: 'src/deck/gallery/city/', + images: toImages(city), + }, + { + id: 'farm', + label: 'Farm', + entityName: entityById('chateau').name, + accent: 'clay', + folder: 'src/deck/gallery/farm/', + images: toImages(farm), + }, + { + id: 'festival', + label: 'Festival', + entityName: entityById('festivals').name, + accent: 'rose', + folder: 'src/deck/gallery/festival/', + images: toImages(festival), + }, +] diff --git a/src/deck/gallery/README.md b/src/deck/gallery/README.md new file mode 100644 index 0000000..e5a79b4 --- /dev/null +++ b/src/deck/gallery/README.md @@ -0,0 +1,18 @@ +# Deck gallery — just drop images in + +Each subfolder feeds one section of the Gallery slide. **Drop image files in and +they appear automatically** — no code to touch. + +| Folder | Section | Entity | +| ----------- | -------- | --------------------- | +| `city/` | City | SolLunar Punk Society | +| `farm/` | Farm | Chateau du Faune | +| `festival/` | Festival | Pop-up Festivals | + +- Supported formats: `jpg` `jpeg` `png` `webp` `avif` `gif` `svg`. +- Images display **sorted by filename** — prefix `01-`, `02-`, … to order them. +- The `*-placeholder.svg` files are demo art. **Delete them** once you add real photos. +- Wiring lives in `src/deck/gallery.ts` (Vite `import.meta.glob`). If you add a + brand-new format, extend the glob patterns there. + +Recommended: web-optimized `.webp` under ~500 KB each for fast loading. diff --git a/src/deck/gallery/city/1024-580.webp b/src/deck/gallery/city/1024-580.webp new file mode 100644 index 0000000..e656aaf Binary files /dev/null and b/src/deck/gallery/city/1024-580.webp differ diff --git a/src/deck/gallery/city/1024-581.webp b/src/deck/gallery/city/1024-581.webp new file mode 100644 index 0000000..427ba88 Binary files /dev/null and b/src/deck/gallery/city/1024-581.webp differ diff --git a/src/deck/gallery/city/1024-768.webp b/src/deck/gallery/city/1024-768.webp new file mode 100644 index 0000000..2939d2b Binary files /dev/null and b/src/deck/gallery/city/1024-768.webp differ diff --git a/src/deck/gallery/city/1024-769.webp b/src/deck/gallery/city/1024-769.webp new file mode 100644 index 0000000..98bde8b Binary files /dev/null and b/src/deck/gallery/city/1024-769.webp differ diff --git a/src/deck/gallery/city/1024-770.webp b/src/deck/gallery/city/1024-770.webp new file mode 100644 index 0000000..f98c87e Binary files /dev/null and b/src/deck/gallery/city/1024-770.webp differ diff --git a/src/deck/gallery/farm/1536-2048-max.webp b/src/deck/gallery/farm/1536-2048-max.webp new file mode 100644 index 0000000..925a489 Binary files /dev/null and b/src/deck/gallery/farm/1536-2048-max.webp differ diff --git a/src/deck/gallery/farm/2048-1536-max (1).webp b/src/deck/gallery/farm/2048-1536-max (1).webp new file mode 100644 index 0000000..66ab743 Binary files /dev/null and b/src/deck/gallery/farm/2048-1536-max (1).webp differ diff --git a/src/deck/gallery/farm/2048-1536-max (2).webp b/src/deck/gallery/farm/2048-1536-max (2).webp new file mode 100644 index 0000000..6e2d1d0 Binary files /dev/null and b/src/deck/gallery/farm/2048-1536-max (2).webp differ diff --git a/src/deck/gallery/farm/2048-1536-max (3).webp b/src/deck/gallery/farm/2048-1536-max (3).webp new file mode 100644 index 0000000..40353d9 Binary files /dev/null and b/src/deck/gallery/farm/2048-1536-max (3).webp differ diff --git a/src/deck/gallery/farm/2048-1536-max.webp b/src/deck/gallery/farm/2048-1536-max.webp new file mode 100644 index 0000000..dcb165a Binary files /dev/null and b/src/deck/gallery/farm/2048-1536-max.webp differ diff --git a/src/deck/gallery/farm/PXL_20250907_134308735.webp b/src/deck/gallery/farm/PXL_20250907_134308735.webp new file mode 100644 index 0000000..0b17b7d Binary files /dev/null and b/src/deck/gallery/farm/PXL_20250907_134308735.webp differ diff --git a/src/deck/gallery/farm/PXL_20251121_064432019.webp b/src/deck/gallery/farm/PXL_20251121_064432019.webp new file mode 100644 index 0000000..0397f6a Binary files /dev/null and b/src/deck/gallery/farm/PXL_20251121_064432019.webp differ diff --git a/src/deck/gallery/farm/garden.webp b/src/deck/gallery/farm/garden.webp new file mode 100644 index 0000000..3acaf33 Binary files /dev/null and b/src/deck/gallery/farm/garden.webp differ diff --git a/src/deck/gallery/festival/01-placeholder.svg b/src/deck/gallery/festival/01-placeholder.svg new file mode 100644 index 0000000..73a7b1f --- /dev/null +++ b/src/deck/gallery/festival/01-placeholder.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + Festival + Photo 01 + PLACEHOLDER · 800×800 + diff --git a/src/deck/gallery/festival/02-placeholder.svg b/src/deck/gallery/festival/02-placeholder.svg new file mode 100644 index 0000000..b6c7456 --- /dev/null +++ b/src/deck/gallery/festival/02-placeholder.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + Festival + Photo 02 + PLACEHOLDER · 800×560 + diff --git a/src/deck/gallery/festival/03-placeholder.svg b/src/deck/gallery/festival/03-placeholder.svg new file mode 100644 index 0000000..3951739 --- /dev/null +++ b/src/deck/gallery/festival/03-placeholder.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + Festival + Photo 03 + PLACEHOLDER · 800×1040 + diff --git a/src/deck/gallery/festival/04-placeholder.svg b/src/deck/gallery/festival/04-placeholder.svg new file mode 100644 index 0000000..46ef5c8 --- /dev/null +++ b/src/deck/gallery/festival/04-placeholder.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + Festival + Photo 04 + PLACEHOLDER · 800×700 + diff --git a/src/deck/gallery/festival/05-placeholder.svg b/src/deck/gallery/festival/05-placeholder.svg new file mode 100644 index 0000000..e9ce0cc --- /dev/null +++ b/src/deck/gallery/festival/05-placeholder.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + Festival + Photo 05 + PLACEHOLDER · 800×600 + diff --git a/src/deck/slides/AskSlide.vue b/src/deck/slides/AskSlide.vue new file mode 100644 index 0000000..1a00ade --- /dev/null +++ b/src/deck/slides/AskSlide.vue @@ -0,0 +1,59 @@ + + + diff --git a/src/deck/slides/EcosystemSlide.vue b/src/deck/slides/EcosystemSlide.vue new file mode 100644 index 0000000..be2b577 --- /dev/null +++ b/src/deck/slides/EcosystemSlide.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/deck/slides/EntitiesSlide.vue b/src/deck/slides/EntitiesSlide.vue new file mode 100644 index 0000000..639212b --- /dev/null +++ b/src/deck/slides/EntitiesSlide.vue @@ -0,0 +1,63 @@ + + + diff --git a/src/deck/slides/GallerySlide.vue b/src/deck/slides/GallerySlide.vue new file mode 100644 index 0000000..b059a62 --- /dev/null +++ b/src/deck/slides/GallerySlide.vue @@ -0,0 +1,92 @@ + + + diff --git a/src/deck/slides/MembershipSlide.vue b/src/deck/slides/MembershipSlide.vue new file mode 100644 index 0000000..259b847 --- /dev/null +++ b/src/deck/slides/MembershipSlide.vue @@ -0,0 +1,58 @@ + + + diff --git a/src/deck/slides/NetworkSlide.vue b/src/deck/slides/NetworkSlide.vue new file mode 100644 index 0000000..a2901e7 --- /dev/null +++ b/src/deck/slides/NetworkSlide.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/deck/slides/PartnershipSlide.vue b/src/deck/slides/PartnershipSlide.vue new file mode 100644 index 0000000..045ea4b --- /dev/null +++ b/src/deck/slides/PartnershipSlide.vue @@ -0,0 +1,78 @@ + + + diff --git a/src/deck/slides/PnlSlide.vue b/src/deck/slides/PnlSlide.vue new file mode 100644 index 0000000..b5f0622 --- /dev/null +++ b/src/deck/slides/PnlSlide.vue @@ -0,0 +1,166 @@ + + + + + diff --git a/src/deck/slides/RoadmapSlide.vue b/src/deck/slides/RoadmapSlide.vue new file mode 100644 index 0000000..caf691a --- /dev/null +++ b/src/deck/slides/RoadmapSlide.vue @@ -0,0 +1,37 @@ + + + diff --git a/src/deck/slides/SolLunarSlide.vue b/src/deck/slides/SolLunarSlide.vue new file mode 100644 index 0000000..a91aba3 --- /dev/null +++ b/src/deck/slides/SolLunarSlide.vue @@ -0,0 +1,8 @@ + + + diff --git a/src/deck/slides/SpacesSlide.vue b/src/deck/slides/SpacesSlide.vue new file mode 100644 index 0000000..048fd15 --- /dev/null +++ b/src/deck/slides/SpacesSlide.vue @@ -0,0 +1,41 @@ + + + diff --git a/src/deck/slides/TechSlide.vue b/src/deck/slides/TechSlide.vue new file mode 100644 index 0000000..db7f7d4 --- /dev/null +++ b/src/deck/slides/TechSlide.vue @@ -0,0 +1,27 @@ + + + diff --git a/src/deck/slides/TenantsSlide.vue b/src/deck/slides/TenantsSlide.vue new file mode 100644 index 0000000..8049d3a --- /dev/null +++ b/src/deck/slides/TenantsSlide.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/deck/slides/TitleSlide.vue b/src/deck/slides/TitleSlide.vue new file mode 100644 index 0000000..b8fef18 --- /dev/null +++ b/src/deck/slides/TitleSlide.vue @@ -0,0 +1,49 @@ + + + diff --git a/src/deck/slides/VisionSlide.vue b/src/deck/slides/VisionSlide.vue new file mode 100644 index 0000000..97f4e42 --- /dev/null +++ b/src/deck/slides/VisionSlide.vue @@ -0,0 +1,34 @@ + + + diff --git a/src/deck/useDeck.ts b/src/deck/useDeck.ts new file mode 100644 index 0000000..6cf2e17 --- /dev/null +++ b/src/deck/useDeck.ts @@ -0,0 +1,66 @@ +import { onMounted, onUnmounted, ref, computed } from 'vue' + +/** + * Deck navigation: current slide index, next/prev/goto, keyboard control, + * and URL-hash sync so any slide is a shareable deep link (…/#/7). + */ +export function useDeck(total: number) { + const clamp = (n: number) => Math.min(Math.max(n, 0), total - 1) + + const readHash = () => { + const m = window.location.hash.match(/#\/(\d+)/) + return m ? clamp(parseInt(m[1], 10) - 1) : 0 + } + + // seed from the hash synchronously so deep links (…/#/7) land on the right + // slide at first paint — no flash of slide 1 and no on-load transition. + const index = ref(readHash()) + const direction = ref<1 | -1>(1) + + function goto(n: number) { + const next = clamp(n) + direction.value = next >= index.value ? 1 : -1 + index.value = next + window.location.hash = `#/${next + 1}` + } + const next = () => goto(index.value + 1) + const prev = () => goto(index.value - 1) + + function onKey(e: KeyboardEvent) { + if (e.key === 'ArrowRight' || e.key === 'PageDown' || e.key === ' ') { + e.preventDefault() + next() + } else if (e.key === 'ArrowLeft' || e.key === 'PageUp') { + e.preventDefault() + prev() + } else if (e.key === 'Home') { + goto(0) + } else if (e.key === 'End') { + goto(total - 1) + } + } + + const onHash = () => { + index.value = readHash() + } + + onMounted(() => { + if (!window.location.hash) window.location.hash = '#/1' + window.addEventListener('keydown', onKey) + window.addEventListener('hashchange', onHash) + }) + onUnmounted(() => { + window.removeEventListener('keydown', onKey) + window.removeEventListener('hashchange', onHash) + }) + + return { + index, + direction, + goto, + next, + prev, + isFirst: computed(() => index.value === 0), + isLast: computed(() => index.value === total - 1), + } +} diff --git a/src/features/deck/DeckView.vue b/src/features/deck/DeckView.vue deleted file mode 100644 index 71419f5..0000000 --- a/src/features/deck/DeckView.vue +++ /dev/null @@ -1,95 +0,0 @@ - - - - - diff --git a/src/features/deck/components/AreaChart.vue b/src/features/deck/components/AreaChart.vue deleted file mode 100644 index 3b6e840..0000000 --- a/src/features/deck/components/AreaChart.vue +++ /dev/null @@ -1,110 +0,0 @@ - - - diff --git a/src/features/deck/components/BarChart.vue b/src/features/deck/components/BarChart.vue deleted file mode 100644 index 7fa1e90..0000000 --- a/src/features/deck/components/BarChart.vue +++ /dev/null @@ -1,40 +0,0 @@ - - - diff --git a/src/features/deck/components/SlideShell.vue b/src/features/deck/components/SlideShell.vue deleted file mode 100644 index 7cadf42..0000000 --- a/src/features/deck/components/SlideShell.vue +++ /dev/null @@ -1,37 +0,0 @@ - - - diff --git a/src/features/deck/components/StackedBar.vue b/src/features/deck/components/StackedBar.vue deleted file mode 100644 index e96f366..0000000 --- a/src/features/deck/components/StackedBar.vue +++ /dev/null @@ -1,33 +0,0 @@ - - - diff --git a/src/features/deck/components/StatCard.vue b/src/features/deck/components/StatCard.vue deleted file mode 100644 index 9f92663..0000000 --- a/src/features/deck/components/StatCard.vue +++ /dev/null @@ -1,25 +0,0 @@ - - - diff --git a/src/features/deck/components/WaterfallChart.vue b/src/features/deck/components/WaterfallChart.vue deleted file mode 100644 index f952abc..0000000 --- a/src/features/deck/components/WaterfallChart.vue +++ /dev/null @@ -1,99 +0,0 @@ - - - diff --git a/src/features/deck/data.ts b/src/features/deck/data.ts deleted file mode 100644 index dae8eca..0000000 --- a/src/features/deck/data.ts +++ /dev/null @@ -1,129 +0,0 @@ -// --------------------------------------------------------------------------- -// 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: 'Ryan', 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 deleted file mode 100644 index 0f3a0e7..0000000 --- a/src/features/deck/slides/01-Title.vue +++ /dev/null @@ -1,41 +0,0 @@ - - - diff --git a/src/features/deck/slides/02-Problem.vue b/src/features/deck/slides/02-Problem.vue deleted file mode 100644 index 2034f18..0000000 --- a/src/features/deck/slides/02-Problem.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - diff --git a/src/features/deck/slides/03-Principles.vue b/src/features/deck/slides/03-Principles.vue deleted file mode 100644 index 01dac2d..0000000 --- a/src/features/deck/slides/03-Principles.vue +++ /dev/null @@ -1,85 +0,0 @@ - - - diff --git a/src/features/deck/slides/04-Project.vue b/src/features/deck/slides/04-Project.vue deleted file mode 100644 index 6e82bc5..0000000 --- a/src/features/deck/slides/04-Project.vue +++ /dev/null @@ -1,69 +0,0 @@ - - - diff --git a/src/features/deck/slides/05-Investment.vue b/src/features/deck/slides/05-Investment.vue deleted file mode 100644 index 8c03f1a..0000000 --- a/src/features/deck/slides/05-Investment.vue +++ /dev/null @@ -1,42 +0,0 @@ - - - diff --git a/src/features/deck/slides/06-Ledger.vue b/src/features/deck/slides/06-Ledger.vue deleted file mode 100644 index 310c78e..0000000 --- a/src/features/deck/slides/06-Ledger.vue +++ /dev/null @@ -1,35 +0,0 @@ - - - diff --git a/src/features/deck/slides/07-Spend.vue b/src/features/deck/slides/07-Spend.vue deleted file mode 100644 index aaded32..0000000 --- a/src/features/deck/slides/07-Spend.vue +++ /dev/null @@ -1,37 +0,0 @@ - - - diff --git a/src/features/deck/slides/08-Servers.vue b/src/features/deck/slides/08-Servers.vue deleted file mode 100644 index 82a6cfd..0000000 --- a/src/features/deck/slides/08-Servers.vue +++ /dev/null @@ -1,57 +0,0 @@ - - - diff --git a/src/features/deck/slides/09-Balances.vue b/src/features/deck/slides/09-Balances.vue deleted file mode 100644 index 3960c00..0000000 --- a/src/features/deck/slides/09-Balances.vue +++ /dev/null @@ -1,82 +0,0 @@ - - - diff --git a/src/features/deck/slides/10-Snapshot.vue b/src/features/deck/slides/10-Snapshot.vue deleted file mode 100644 index 451e62e..0000000 --- a/src/features/deck/slides/10-Snapshot.vue +++ /dev/null @@ -1,29 +0,0 @@ - - - diff --git a/src/features/deck/slides/11-App.vue b/src/features/deck/slides/11-App.vue deleted file mode 100644 index 87a3ae5..0000000 --- a/src/features/deck/slides/11-App.vue +++ /dev/null @@ -1,56 +0,0 @@ - - - diff --git a/src/features/deck/slides/12-Value.vue b/src/features/deck/slides/12-Value.vue deleted file mode 100644 index 75298d6..0000000 --- a/src/features/deck/slides/12-Value.vue +++ /dev/null @@ -1,46 +0,0 @@ - - - diff --git a/src/features/deck/slides/13-Fava.vue b/src/features/deck/slides/13-Fava.vue deleted file mode 100644 index f47dcc0..0000000 --- a/src/features/deck/slides/13-Fava.vue +++ /dev/null @@ -1,68 +0,0 @@ - - - diff --git a/src/features/deck/slides/index.ts b/src/features/deck/slides/index.ts deleted file mode 100644 index 7add588..0000000 --- a/src/features/deck/slides/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -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 SpendSlide from './07-Spend.vue' -import ServersSlide from './08-Servers.vue' -import BalancesSlide from './09-Balances.vue' -import SnapshotSlide from './10-Snapshot.vue' -import AppSlide from './11-App.vue' -import ValueSlide from './12-Value.vue' -import FavaSlide from './13-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: 'Where the money went', component: SpendSlide }, - { 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 deleted file mode 100644 index 3f04345..0000000 --- a/src/features/deck/useDeck.ts +++ /dev/null @@ -1,64 +0,0 @@ -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 ad0397e..1537b8b 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -4,7 +4,7 @@ const routes: RouteRecordRaw[] = [ { path: '/', name: 'deck', - component: () => import('@/features/deck/DeckView.vue'), + component: () => import('@/views/DeckView.vue'), }, ] diff --git a/src/style.css b/src/style.css index 4c32d12..f10e40b 100644 --- a/src/style.css +++ b/src/style.css @@ -73,6 +73,43 @@ --radius-sm: calc(var(--radius) - 4px); } +/* ───────────────────────────────────────────────────────────── + ARCHIPELAGO pitch-deck brand palette (PLACEHOLDER — swap when real + brand assets land). Regenerative-tech: pine green + warm amber + on a deep near-black ground. Used by the self-contained dark + deck; the shadcn tokens above are left intact for other pages. + ───────────────────────────────────────────────────────────── */ +@theme { + --color-ink: hsl(160 28% 6%); /* deck ground */ + --color-ink-2: hsl(160 24% 9%); /* raised panels */ + --color-ink-3: hsl(158 20% 13%); /* cards / borders */ + + --color-pine-300: hsl(158 42% 62%); + --color-pine-400: hsl(159 45% 50%); + --color-pine-500: hsl(160 60% 40%); + --color-pine-600: hsl(162 66% 30%); + + --color-amber-300: hsl(38 96% 70%); + --color-amber-400: hsl(36 95% 60%); + --color-amber-500: hsl(32 92% 52%); + + /* SolLunar Punk Society (city) accent — lunarpunk indigo */ + --color-indigo-300: hsl(255 85% 78%); + --color-indigo-400: hsl(256 78% 68%); + --color-indigo-500: hsl(258 70% 58%); + + /* Pop-up Festivals accent — vibrant rave rose */ + --color-rose-300: hsl(342 95% 76%); + --color-rose-400: hsl(344 85% 66%); + --color-rose-500: hsl(346 78% 56%); + + --color-clay-400: hsl(14 72% 62%); /* farm / warmth accent */ + --color-sand: hsl(44 30% 90%); /* primary deck text */ + --color-sand-dim: hsl(150 8% 62%); /* muted deck text */ + + --font-display: 'Georgia', 'Times New Roman', serif; +} + @layer base { * { @apply border-border; @@ -80,10 +117,13 @@ 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); + + /* Deck root: self-contained dark canvas independent of shadcn tokens */ + .deck-root { + background: + radial-gradient(120% 80% at 80% -10%, hsl(162 60% 14% / 0.55), transparent 60%), + radial-gradient(90% 70% at -10% 110%, hsl(32 80% 18% / 0.35), transparent 55%), + var(--color-ink); + color: var(--color-sand); } } diff --git a/src/views/DeckView.vue b/src/views/DeckView.vue new file mode 100644 index 0000000..6133717 --- /dev/null +++ b/src/views/DeckView.vue @@ -0,0 +1,7 @@ + + +