From 3634509ed43b11d63aefbf6643f817aa85888b98 Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Fri, 20 Feb 2026 14:44:39 -0500 Subject: [PATCH] fix(machine): mobile UI polish and shadcn-vue ScrollArea - Use h-dvh instead of h-screen for mobile browser viewport - Add ScrollArea component (shadcn-vue) to replace native scrollbars - Move Hide Debug into debug bar row, show Show Debug only when off - Responsive cash-out denomination grid (single col on mobile) - Add safe-area padding for mobile gesture bar - Add business model documentation Co-Authored-By: Claude Opus 4.6 --- apps/machine/src/App.vue | 14 +- .../components/ui/scroll-area/ScrollArea.vue | 29 + .../components/ui/scroll-area/ScrollBar.vue | 33 + .../src/components/ui/scroll-area/index.ts | 2 + apps/machine/src/views/CashInView.vue | 605 +++++++++--------- apps/machine/src/views/CashOutView.vue | 474 +++++++------- apps/machine/src/views/IdleView.vue | 9 +- docs/business-model.md | 121 ++++ 8 files changed, 756 insertions(+), 531 deletions(-) create mode 100644 apps/machine/src/components/ui/scroll-area/ScrollArea.vue create mode 100644 apps/machine/src/components/ui/scroll-area/ScrollBar.vue create mode 100644 apps/machine/src/components/ui/scroll-area/index.ts create mode 100644 docs/business-model.md diff --git a/apps/machine/src/App.vue b/apps/machine/src/App.vue index f165310..90115e7 100644 --- a/apps/machine/src/App.vue +++ b/apps/machine/src/App.vue @@ -31,9 +31,7 @@ function toggleLiveServices() { diff --git a/apps/machine/src/views/CashOutView.vue b/apps/machine/src/views/CashOutView.vue index 60fb7b0..2c6948f 100644 --- a/apps/machine/src/views/CashOutView.vue +++ b/apps/machine/src/views/CashOutView.vue @@ -7,6 +7,7 @@ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/com import { Badge } from '@/components/ui/badge' import { Alert, AlertDescription } from '@/components/ui/alert' import { Skeleton } from '@/components/ui/skeleton' +import { ScrollArea } from '@/components/ui/scroll-area' import QRCode from '@/components/QRCode.vue' const router = useRouter() @@ -105,247 +106,262 @@ function formatFiat(cents: number): string { -
- - - - -

Fetching exchange rate...

-
-
+ +
+ + + + +

Fetching exchange rate...

+
+
- - - - Select Cash Amount - - Rate: {{ formatSats(context.exchangeRate) }} sats/$ - - - - -
-
- ${{ denom }} -
- - {{ selectionCounts[denom] || 0 }} - + {{ + selectionCounts[denom] || 0 + }} + +
+
+
+ + +
+

You will receive

+

+ ${{ context ? formatFiat(context.fiatAmount) : '0.00' }} +

+

+ You pay: + + {{ context ? formatSats(context.satsAmount) : 0 }} sats + +

+
+ + +
+ + +
+ + + + +

+ Select the cash amount you want to receive. You'll pay the equivalent in Bitcoin. +

+
+
+
+
+ + + + + +

Creating invoice...

+
+
+ + + + + Scan to Pay + + Pay {{ formatSats(context.satsAmount) }} sats to + receive ${{ formatFiat(context.fiatAmount) }} + + + + +
+ +

+ Scan with any Lightning wallet +

+
+ + +
+
+
⏳
+
+

Waiting for payment...

+
+ + +
+

+ {{ context.invoice.slice(0, 50) }}... +

+
+
- - -
-

You will receive

-

- ${{ context ? formatFiat(context.fiatAmount) : '0.00' }} -

-

- You pay: - - {{ context ? formatSats(context.satsAmount) : 0 }} sats - -

-
+ + + +

Dev: Use alice-pay or button below

+ +
+
+
+
- -
+ + + + +

Dispensing cash...

+

Please wait

+
+
+ + + + +
✅
+ Cash Ready! +

+ Please take your + ${{ formatFiat(context.fiatAmount) }} +

+
+
+ + + + + Would you like a receipt? +

+ Scan your npub to receive a Nostr DM receipt +

+
+

📱 Scan npub

+
+ +
+
+ + + + +
🎉
+ Transaction Complete! +

Thank you!

+
+
+ + + + +
⚠️
+ Error +

{{ context?.error || 'An error occurred' }}

-
+ + - - - -

- Select the cash amount you want to receive. You'll pay the equivalent in Bitcoin. -

-
-
- - - - - - - -

Creating invoice...

-
-
- - - - - Scan to Pay - - Pay {{ formatSats(context.satsAmount) }} sats to - receive ${{ formatFiat(context.fiatAmount) }} - - - - -
- -

- Scan with any Lightning wallet -

-
- - -
-
-
⏳
-
-

Waiting for payment...

-
- - -
-

- {{ context.invoice.slice(0, 50) }}... -

-
- -
-
- - - - -

Dev: Use alice-pay or button below

- -
-
-
-
- - - - - -

Dispensing cash...

-

Please wait

-
-
- - - - -
✅
- Cash Ready! -

- Please take your - ${{ formatFiat(context.fiatAmount) }} -

- -
-
- - - - - Would you like a receipt? -

Scan your npub to receive a Nostr DM receipt

-
-

📱 Scan npub

-
- -
-
- - - - -
🎉
- Transaction Complete! -

Thank you!

-
-
- - - - -
⚠️
- Error -

{{ context?.error || 'An error occurred' }}

- -
-
- - - - -

State: {{ nestedState }}

-
-
-
+ + + +

State: {{ nestedState }}

+
+
+
+ diff --git a/apps/machine/src/views/IdleView.vue b/apps/machine/src/views/IdleView.vue index 650dec8..9496c5f 100644 --- a/apps/machine/src/views/IdleView.vue +++ b/apps/machine/src/views/IdleView.vue @@ -59,7 +59,9 @@ function handleCashOut() { diff --git a/docs/business-model.md b/docs/business-model.md new file mode 100644 index 0000000..f42468e --- /dev/null +++ b/docs/business-model.md @@ -0,0 +1,121 @@ +# Business Model + +How we place ATMs at locations and make them sustainable for everyone involved. + +## Principles + +- **No KYC** — more users, less friction, preserves privacy +- **Good energy locations** — we seek places with community trust and genuine intent +- **Bitcoin-native infrastructure** — Lightning, Nostr, Cashu — no legacy payment rails +- **Operator sovereignty** — locations can choose how much they want to manage themselves + +## Deployment Models + +### Managed (we run it) + +We own the machine and handle all operations. The location provides power, internet, and floor space. + +| Aspect | Details | +| ------------------- | ----------------------------------------- | +| Machine ownership | Us | +| Liquidity | Our Lightning channels + cash servicing | +| Software updates | Us (remote via Nostr) | +| Cash management | Us (periodic servicing visits) | +| Fee configuration | Us | +| Revenue to location | Fixed monthly hosting fee OR % of tx fees | + +Best for locations that want passive income with zero involvement. + +### Self-Sovereign (they run it) + +The location buys (or finances) the machine and runs their own stack. + +| Aspect | Details | +| ----------------- | ---------------------------------------- | +| Machine ownership | Them | +| Liquidity | Their own Lightning node + Lightning.Pub | +| Software updates | Them (we provide releases) | +| Cash management | Them | +| Fee configuration | Them | +| Revenue | 100% theirs | + +Best for bitcoiners who want to run infrastructure and keep full control. We provide the software, hardware, and support. + +### Machine Financing + +For the self-sovereign model, we can offer financing: + +- We purchase the machine upfront +- Location pays it off over time (from ATM revenue) +- Once paid off, they own it outright +- We provide support during the financing period + +## Revenue Models + +### Fixed Hosting Fee + +- We pay the location a flat monthly fee for hosting +- Simpler accounting, predictable income for location +- We take all transaction fee revenue +- Works well for locations that just want passive income + +### Profit Sharing + +- Location receives a % of transaction fees +- Aligns incentives — they benefit from higher volume +- More attractive to engaged locations +- Requires transparent reporting (could be on-chain/Nostr-verifiable) + +### Foot Traffic Value + +Beyond direct revenue, the ATM provides: + +- People visit specifically to use the ATM, then browse/buy at the location +- Bitcoin-friendly branding signals to the community +- Conversation starter — draws curious newcomers +- Community hub potential — meetup anchor point + +## Location Criteria + +We're selective about where we place machines. The goal is to find locations with **good energy** — places where the community is genuine and the environment discourages misuse. + +### Ideal locations + +- Coffee shops, cafes, local restaurants +- Co-working spaces, maker spaces +- Bitcoin meetup venues, hackerspaces +- Surf shops, yoga studios, wellness centers +- Local/independent businesses (not chains) +- Places where the owner is curious about or already into Bitcoin + +### What we look for + +- Owner alignment — understands or wants to understand Bitcoin +- Community trust — regulars know each other, self-policing +- Foot traffic — enough people to make the machine worthwhile +- Stable internet + power +- Visible but not exposed — inside the business, not on the street + +### What we avoid + +- High-crime areas +- Anonymous/transient locations (gas stations on highways, etc.) +- Businesses with no community connection +- Anywhere the owner is purely financially motivated with no alignment + +## Technical Requirements + +For the managed model to work at scale, we need: + +- **Remote monitoring** — machine status, cash levels, error alerts over [[clink-protocol|CLINK]]/Nostr +- **Operator dashboard** — manage cassettes, view transactions, update config remotely +- **Multi-machine support** — one Lightning.Pub instance serving multiple ATMs +- **Fee configuration** — per-machine fee % set by operator +- **Availability broadcast** — public Nostr events indicating cash-in/cash-out availability (see [[ndebit-cash-in-flow|cash-in flow]]) + +## Related + +- [[device-configuration]] — hardware setup for different machine models +- [[machine-installation]] — NixOS installation on ATM hardware +- [[clink-protocol]] — CLINK protocol for ATM-wallet communication +- [[ndebit-cash-in-flow]] — cash-in payment flow