feat(machine): redesign idle screen with circular touch zones

Full-canvas layout: brand zone with logo/badges/balance at top,
two circular touch zones (Buy=bitcoin orange, Sell=green) with
zone-glow animation. Show per-flow commission rates (Buy/Sell).
Move light/dark toggle to App.vue for omnipresence.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Patrick Mulligan 2026-03-01 12:12:09 -05:00
commit 263165b495

View file

@ -3,16 +3,14 @@ import { ref, computed, watch } from 'vue'
import { useRouter } from 'vue-router'
import { nip19 } from 'nostr-tools'
import { useAtmStore } from '@/stores/atm'
import { useTheme } from '@/composables/useTheme'
import { initialContext } from '@lamassu/state-machine'
import { Button } from '@/components/ui/button'
import { Card, CardContent } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import BitcoinIcon from '@/components/BitcoinIcon.vue'
import QRCode from '@/components/QRCode.vue'
const router = useRouter()
const atmStore = useAtmStore()
const { colorMode } = useTheme()
const lndconnectUrl = import.meta.env.VITE_LNDCONNECT_URL || ''
const showZeusQR = ref(false)
const showLpQR = ref(false)
@ -61,75 +59,82 @@ function handleCashOut() {
</script>
<template>
<div
class="flex min-h-0 flex-1 flex-col items-center justify-center gap-4 overflow-auto p-4 sm:gap-6 md:gap-8 md:p-8"
>
<!-- Logo -->
<div class="flex flex-col items-center gap-1 sm:gap-2">
<div class="flex min-h-0 flex-1 flex-col">
<!-- Brand Zone (top ~30%) -->
<div class="flex flex-col items-center justify-center gap-2 pt-[4vh] pb-[2vh]">
<img
src="/logo.png"
alt="Lamassu"
class="h-20 w-20 animate-float drop-shadow-[0_0_24px_var(--primary)] sm:h-28 sm:w-28 md:h-40 md:w-40"
class="h-[12vh] w-[12vh] animate-float drop-shadow-[0_0_24px_var(--primary)]"
/>
<h1 class="text-2xl font-bold text-primary sm:text-3xl md:text-4xl">Bitcoinmat</h1>
<!-- Footer badges -->
<div class="mt-2 flex flex-wrap items-center justify-center gap-2 sm:mt-4 sm:gap-4">
<Badge variant="outline" class="border-success text-success text-xs sm:text-sm"
>No KYC</Badge
>
<Badge variant="outline" class="border-primary text-primary text-xs sm:text-sm"
>No Registration</Badge
>
<Badge variant="outline" class="border-bitcoin text-bitcoin text-xs sm:text-sm"
>Just Bitcoin</Badge
>
<h1 class="text-[5vh] font-bold text-primary leading-tight">Bitcoinmat</h1>
<div class="flex flex-wrap items-center justify-center gap-3">
<Badge variant="outline" class="border-success text-success text-lg">No KYC</Badge>
<Badge variant="outline" class="border-primary text-primary text-lg">No Registration</Badge>
<Badge variant="outline" class="border-bitcoin text-bitcoin text-lg">Just Bitcoin</Badge>
</div>
<!-- Balance + Commission rates -->
<div
class="mt-1 flex flex-wrap items-center justify-center gap-4 text-[2vh] text-muted-foreground"
>
<span v-if="atmStore.balanceSats !== null">
Available:
<span class="font-bold text-primary"
>{{ atmStore.balanceSats.toLocaleString() }} sats</span
>
</span>
<span>
Buy:
<span class="font-bold text-bitcoin"
>{{
(
(atmStore.context?.cashInFeePercent ?? initialContext.cashInFeePercent) * 100
).toFixed(2)
}}%</span
>
</span>
<span>
Sell:
<span class="font-bold text-success"
>{{
(
(atmStore.context?.cashOutFeePercent ?? initialContext.cashOutFeePercent) * 100
).toFixed(2)
}}%</span
>
</span>
</div>
</div>
<!-- Balance -->
<div v-if="atmStore.balanceSats !== null" class="text-center">
<p class="text-sm text-muted-foreground">Available Balance</p>
<p class="text-xl font-bold text-primary sm:text-2xl">
{{ atmStore.balanceSats.toLocaleString() }} sats
</p>
</div>
<!-- Action Buttons -->
<div class="flex flex-col gap-4 sm:flex-row sm:gap-6 md:gap-8">
<!-- Touch Zones (fills remaining space) -->
<div class="flex flex-1 items-center justify-center gap-[8vw] pb-[4vh]">
<!-- Buy Bitcoin -->
<Card
class="h-40 w-40 cursor-pointer border-2 border-success/50 bg-success/20 transition-all hover:scale-105 hover:border-success hover:bg-success/30 active:scale-95 sm:h-52 sm:w-52 md:h-64 md:w-64"
<button
class="flex aspect-square w-[36vh] flex-col items-center justify-center gap-4 rounded-full border-2 border-bitcoin/50 bg-bitcoin/10 transition-all active:scale-[0.97] animate-zone-glow"
style="--glow-color: var(--bitcoin)"
@click="handleCashIn"
>
<CardContent
class="flex h-full flex-col items-center justify-center gap-2 p-4 sm:gap-3 sm:p-6"
>
<BitcoinIcon :size="48" class="shrink-0 sm:hidden" />
<BitcoinIcon :size="64" class="hidden shrink-0 sm:block" />
<span class="text-lg font-bold text-success sm:text-xl">Buy Bitcoin</span>
<span class="hidden text-sm text-foreground/70 sm:block">Insert cash, receive sats</span>
</CardContent>
</Card>
<BitcoinIcon :size="100" class="shrink-0" />
<span class="text-[3.5vh] font-bold text-bitcoin">Buy Bitcoin</span>
<span class="text-[1.8vh] text-foreground/70">Insert cash, receive sats</span>
</button>
<!-- Sell Bitcoin -->
<Card
class="h-40 w-40 cursor-pointer border-2 border-warning/50 bg-warning/20 transition-all hover:scale-105 hover:border-warning hover:bg-warning/30 active:scale-95 sm:h-52 sm:w-52 md:h-64 md:w-64"
<button
class="flex aspect-square w-[36vh] flex-col items-center justify-center gap-4 rounded-full border-2 border-success/50 bg-success/10 transition-all active:scale-[0.97] animate-zone-glow"
style="--glow-color: var(--success)"
@click="handleCashOut"
>
<CardContent
class="flex h-full flex-col items-center justify-center gap-2 p-4 sm:gap-3 sm:p-6"
>
<span class="text-5xl sm:text-7xl">💵</span>
<span class="text-lg font-bold text-warning sm:text-xl">Sell Bitcoin</span>
<span class="hidden text-sm text-foreground/70 sm:block">Pay invoice, receive cash</span>
</CardContent>
</Card>
<span class="text-[8vh] leading-none">💵</span>
<span class="text-[3.5vh] font-bold text-success">Sell Bitcoin</span>
<span class="text-[1.8vh] text-foreground/70">Pay invoice, receive cash</span>
</button>
</div>
<!-- Dev QR buttons -->
<div
v-if="atmStore.debugMode"
class="flex flex-wrap items-center justify-center gap-2 sm:gap-4"
class="absolute bottom-16 left-1/2 -translate-x-1/2 flex flex-wrap items-center justify-center gap-4"
>
<Button
v-if="lndconnectUrl"
@ -159,8 +164,7 @@ function handleCashOut() {
>
<p class="text-sm text-white/70">Scan with Zeus to connect to lnd-alice</p>
<div class="rounded-2xl">
<QRCode :value="lndconnectUrl" :size="280" class="sm:hidden" />
<QRCode :value="lndconnectUrl" :size="400" class="hidden sm:block" />
<QRCode :value="lndconnectUrl" :size="400" />
</div>
<div class="flex items-center gap-2">
<Button
@ -185,10 +189,9 @@ function handleCashOut() {
>
<p class="text-sm text-white/70">Lightning.Pub nprofile</p>
<div class="rounded-2xl">
<QRCode :value="lpNprofile" :size="280" class="sm:hidden" />
<QRCode :value="lpNprofile" :size="400" class="hidden sm:block" />
<QRCode :value="lpNprofile" :size="400" />
</div>
<code class="max-w-[90vw] truncate text-xs text-white/50 sm:max-w-md">{{ lpNprofile }}</code>
<code class="max-w-[90vw] truncate text-xs text-white/50">{{ lpNprofile }}</code>
<div class="flex items-center gap-2">
<Button variant="outline" size="sm" class="text-white" @click="copyToClipboard(lpNprofile)">
{{ copied ? 'Copied!' : 'Copy' }}
@ -199,22 +202,12 @@ function handleCashOut() {
</div>
</div>
<!-- Light/dark toggle -->
<Button
variant="ghost"
size="sm"
class="absolute bottom-4 left-4 text-xs text-muted-foreground"
@click="colorMode = colorMode === 'dark' ? 'light' : 'dark'"
>
{{ colorMode === 'dark' ? '☀' : '☾' }}
</Button>
<!-- Debug toggle (only visible when debug bar is hidden, never in production) -->
<Button
v-if="!atmStore.debugMode && atmStore.allowMockFallback"
variant="ghost"
size="sm"
class="absolute bottom-4 right-4 text-xs text-muted-foreground"
size="kiosk"
class="absolute bottom-6 right-6 text-lg text-muted-foreground"
@click="atmStore.toggleDebug"
>
Show Debug