fix(machine): add mobile responsive layout for atm.aiolabs.dev
The kiosk UI was optimized for 1920px touchscreens, breaking mobile web. Uses Tailwind lg: breakpoints (mobile-first) so mobile works naturally while kiosk sizing applies at 1024px+. No JS branching — pure CSS. - Viewport: width=1920 → width=device-width - Kiosk-only: overflow:hidden and cursor:none behind @media (min-width: 1024px) - IdleView: stack buttons vertically on mobile, justify-around for even spacing - CashInView/CashOutView: stack split panels on mobile (flex-col-reverse lg:flex-row) - QRCode: SVG scales down on small screens via max-w-full - App.vue: hide verbose badges on mobile, debug bar collapses theme/color selectors - Connection/network badges always visible (not gated by isIdle) - Reduce zone-glow animation intensity by half (20px/4px vs 40px/8px) - All lg: values verified to match original kiosk rendering Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
624650d3be
commit
7a42380f0e
7 changed files with 257 additions and 149 deletions
|
|
@ -3,7 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=1920, initial-scale=1.0, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
|
||||||
<title>Lamassu ATM</title>
|
<title>Lamassu ATM</title>
|
||||||
<style>
|
<style>
|
||||||
/* Prevent text selection and context menu on kiosk */
|
/* Prevent text selection and context menu on kiosk */
|
||||||
|
|
@ -17,10 +17,16 @@
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
|
||||||
background: #000;
|
background: #000;
|
||||||
|
}
|
||||||
|
/* Kiosk-only: lock overflow and hide cursor */
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
overflow: hidden;
|
||||||
cursor: none;
|
cursor: none;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,9 @@ function toggleLiveServices() {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex h-dvh w-screen flex-col overflow-hidden bg-background font-sans text-foreground">
|
<div
|
||||||
|
class="flex min-h-dvh lg:h-dvh w-screen flex-col overflow-y-auto lg:overflow-hidden bg-background font-sans text-foreground"
|
||||||
|
>
|
||||||
<!-- Maintenance screen: shown when initialization fails in production -->
|
<!-- Maintenance screen: shown when initialization fails in production -->
|
||||||
<div
|
<div
|
||||||
v-if="atmStore.initError"
|
v-if="atmStore.initError"
|
||||||
|
|
@ -77,8 +79,8 @@ function toggleLiveServices() {
|
||||||
<line x1="12" y1="9" x2="12" y2="13" />
|
<line x1="12" y1="9" x2="12" y2="13" />
|
||||||
<line x1="12" y1="17" x2="12.01" y2="17" />
|
<line x1="12" y1="17" x2="12.01" y2="17" />
|
||||||
</svg>
|
</svg>
|
||||||
<h1 class="text-[3.5rem] font-bold">ATM Unavailable</h1>
|
<h1 class="text-2xl lg:text-[3.5rem] font-bold">ATM Unavailable</h1>
|
||||||
<p class="max-w-md text-center text-2xl text-muted-foreground">
|
<p class="max-w-md text-center text-base lg:text-2xl text-muted-foreground">
|
||||||
This machine is temporarily out of service. Please try again later or use another machine.
|
This machine is temporarily out of service. Please try again later or use another machine.
|
||||||
</p>
|
</p>
|
||||||
<p v-if="atmStore.debugMode" class="max-w-lg text-center font-mono text-sm text-destructive">
|
<p v-if="atmStore.debugMode" class="max-w-lg text-center font-mono text-sm text-destructive">
|
||||||
|
|
@ -89,42 +91,47 @@ function toggleLiveServices() {
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<router-view />
|
<router-view />
|
||||||
|
|
||||||
<!-- Connection status + balance (top right, idle only) -->
|
<!-- Connection status + network (always visible, top right) -->
|
||||||
<div
|
<div
|
||||||
v-if="atmStore.isIdle"
|
class="fixed right-2 top-2 lg:right-6 lg:top-6 z-50 flex flex-wrap items-center justify-end gap-1 lg:gap-2"
|
||||||
class="fixed right-6 top-6 z-50 flex flex-wrap items-center justify-end gap-2"
|
|
||||||
>
|
>
|
||||||
|
<!-- Price and balance: only on idle, hidden on mobile -->
|
||||||
<Badge
|
<Badge
|
||||||
v-if="formattedBtcPrice"
|
v-if="atmStore.isIdle && formattedBtcPrice"
|
||||||
class="bg-primary/10 font-mono text-primary border border-primary/30 text-base"
|
class="hidden lg:inline-flex bg-primary/10 font-mono text-primary border border-primary/30 text-base"
|
||||||
>
|
>
|
||||||
{{ formattedBtcPrice }}
|
{{ formattedBtcPrice }}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge
|
<Badge
|
||||||
v-if="atmStore.balanceSats !== null"
|
v-if="atmStore.isIdle && atmStore.balanceSats !== null"
|
||||||
class="bg-bitcoin/20 font-mono text-bitcoin border border-bitcoin/30 text-base"
|
class="hidden lg:inline-flex bg-bitcoin/20 font-mono text-bitcoin border border-bitcoin/30 text-base"
|
||||||
>
|
>
|
||||||
bal: {{ formatSats(atmStore.balanceSats) }} sats
|
bal: {{ formatSats(atmStore.balanceSats) }} sats
|
||||||
</Badge>
|
</Badge>
|
||||||
|
<!-- Connection status: always visible -->
|
||||||
<Badge
|
<Badge
|
||||||
v-if="atmStore.connectionStatus === 'connected'"
|
v-if="atmStore.connectionStatus === 'connected'"
|
||||||
class="bg-success text-success-foreground"
|
class="bg-success text-success-foreground text-[10px] lg:text-xs"
|
||||||
>
|
>
|
||||||
Live
|
Live
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge
|
<Badge
|
||||||
v-else-if="atmStore.connectionStatus === 'connecting'"
|
v-else-if="atmStore.connectionStatus === 'connecting'"
|
||||||
class="bg-warning text-warning-foreground"
|
class="bg-warning text-warning-foreground text-[10px] lg:text-xs"
|
||||||
>
|
>
|
||||||
Connecting...
|
Connecting...
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge v-else-if="atmStore.connectionStatus === 'error'" class="bg-destructive">
|
<Badge
|
||||||
|
v-else-if="atmStore.connectionStatus === 'error'"
|
||||||
|
class="bg-destructive text-[10px] lg:text-xs"
|
||||||
|
>
|
||||||
Offline
|
Offline
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge v-else class="bg-muted text-muted-foreground">Mock</Badge>
|
<Badge v-else class="bg-muted text-muted-foreground text-[10px] lg:text-xs">Mock</Badge>
|
||||||
|
<!-- Network badge: always visible when not mainnet -->
|
||||||
<Badge
|
<Badge
|
||||||
v-if="atmStore.network && atmStore.network !== 'mainnet'"
|
v-if="atmStore.network && atmStore.network !== 'mainnet'"
|
||||||
class="bg-warning/20 text-warning border border-warning/30"
|
class="bg-warning/20 text-warning border border-warning/30 text-[10px] lg:text-xs"
|
||||||
>
|
>
|
||||||
{{ atmStore.network }}
|
{{ atmStore.network }}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
@ -134,12 +141,11 @@ function toggleLiveServices() {
|
||||||
<Button
|
<Button
|
||||||
v-if="!atmStore.allowMockFallback"
|
v-if="!atmStore.allowMockFallback"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="kiosk"
|
class="fixed bottom-3 right-3 lg:bottom-6 lg:right-6 z-50 h-10 px-3 py-1 text-sm rounded-lg lg:h-[7vh] lg:min-h-[70px] lg:px-8 lg:py-3 lg:text-2xl lg:rounded-xl gap-2 lg:gap-3"
|
||||||
class="fixed bottom-6 right-6 z-50 gap-3"
|
|
||||||
@click="colorMode = colorMode === 'dark' ? 'light' : 'dark'"
|
@click="colorMode = colorMode === 'dark' ? 'light' : 'dark'"
|
||||||
>
|
>
|
||||||
<Sun v-if="colorMode === 'dark'" :size="28" />
|
<Sun v-if="colorMode === 'dark'" class="w-5 h-5 lg:w-7 lg:h-7" />
|
||||||
<Moon v-else :size="28" />
|
<Moon v-else class="w-5 h-5 lg:w-7 lg:h-7" />
|
||||||
{{ colorMode === 'dark' ? 'Light' : 'Dark' }}
|
{{ colorMode === 'dark' ? 'Light' : 'Dark' }}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
|
@ -148,7 +154,8 @@ function toggleLiveServices() {
|
||||||
v-if="atmStore.debugMode"
|
v-if="atmStore.debugMode"
|
||||||
class="shrink-0 bg-popover pb-[env(safe-area-inset-bottom)] font-mono text-xs text-popover-foreground"
|
class="shrink-0 bg-popover pb-[env(safe-area-inset-bottom)] font-mono text-xs text-popover-foreground"
|
||||||
>
|
>
|
||||||
<div class="flex flex-wrap items-center gap-2 px-4 py-2 sm:gap-4">
|
<!-- Compact bar: always visible -->
|
||||||
|
<div class="flex flex-wrap items-center gap-2 lg:gap-4 px-3 py-1.5 lg:px-4 lg:py-2">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
@ -157,11 +164,17 @@ function toggleLiveServices() {
|
||||||
>
|
>
|
||||||
{{ debugExpanded ? '▼' : '▲' }}
|
{{ debugExpanded ? '▼' : '▲' }}
|
||||||
</Button>
|
</Button>
|
||||||
<span class="text-success">State: {{ atmStore.currentState }}</span>
|
<span class="text-success text-[10px] lg:text-xs">{{ atmStore.currentState }}</span>
|
||||||
<Button variant="outline" size="sm" class="h-6 text-xs" @click="toggleLiveServices">
|
<Button
|
||||||
{{ atmStore.useLiveServices ? 'Switch to Mock' : 'Connect Live' }}
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
class="h-5 lg:h-6 text-[10px] lg:text-xs"
|
||||||
|
@click="toggleLiveServices"
|
||||||
|
>
|
||||||
|
{{ atmStore.useLiveServices ? 'Mock' : 'Live' }}
|
||||||
</Button>
|
</Button>
|
||||||
<div class="flex flex-wrap items-center gap-1">
|
<!-- Theme + color mode: hidden on mobile, shown inline on desktop -->
|
||||||
|
<div class="hidden lg:flex flex-wrap items-center gap-1">
|
||||||
<Button
|
<Button
|
||||||
v-for="theme in themes"
|
v-for="theme in themes"
|
||||||
:key="theme.id"
|
:key="theme.id"
|
||||||
|
|
@ -173,7 +186,7 @@ function toggleLiveServices() {
|
||||||
{{ theme.label }}
|
{{ theme.label }}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1">
|
<div class="hidden lg:flex items-center gap-1">
|
||||||
<Button
|
<Button
|
||||||
:variant="colorMode === 'light' ? 'default' : 'outline'"
|
:variant="colorMode === 'light' ? 'default' : 'outline'"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
@ -202,14 +215,60 @@ function toggleLiveServices() {
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
class="ml-auto h-6 text-xs text-muted-foreground"
|
class="ml-auto h-5 lg:h-6 text-[10px] lg:text-xs text-muted-foreground"
|
||||||
@click="atmStore.toggleDebug"
|
@click="atmStore.toggleDebug"
|
||||||
>
|
>
|
||||||
Hide Debug
|
Hide
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="debugExpanded" class="max-h-48 overflow-auto px-4 pb-4">
|
<!-- Expanded section: theme/color on mobile + JSON context -->
|
||||||
<pre class="text-muted-foreground">{{ JSON.stringify(atmStore.context, null, 2) }}</pre>
|
<div v-if="debugExpanded" class="px-3 pb-3 lg:px-4 lg:pb-4 space-y-2">
|
||||||
|
<!-- Theme + color mode on mobile (hidden on desktop since shown above) -->
|
||||||
|
<div class="lg:hidden space-y-2">
|
||||||
|
<div class="flex flex-wrap items-center gap-1">
|
||||||
|
<Button
|
||||||
|
v-for="theme in themes"
|
||||||
|
:key="'m-' + theme.id"
|
||||||
|
:variant="currentTheme === theme.id ? 'default' : 'outline'"
|
||||||
|
size="sm"
|
||||||
|
class="h-5 text-[10px]"
|
||||||
|
@click="currentTheme = theme.id"
|
||||||
|
>
|
||||||
|
{{ theme.label }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<Button
|
||||||
|
:variant="colorMode === 'light' ? 'default' : 'outline'"
|
||||||
|
size="sm"
|
||||||
|
class="h-5 text-[10px]"
|
||||||
|
@click="colorMode = 'light'"
|
||||||
|
>
|
||||||
|
Light
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
:variant="colorMode === 'dark' ? 'default' : 'outline'"
|
||||||
|
size="sm"
|
||||||
|
class="h-5 text-[10px]"
|
||||||
|
@click="colorMode = 'dark'"
|
||||||
|
>
|
||||||
|
Dark
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
:variant="colorMode === 'system' ? 'default' : 'outline'"
|
||||||
|
size="sm"
|
||||||
|
class="h-5 text-[10px]"
|
||||||
|
@click="colorMode = 'system'"
|
||||||
|
>
|
||||||
|
System
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="max-h-48 overflow-auto">
|
||||||
|
<pre class="text-muted-foreground text-[10px] lg:text-xs">{{
|
||||||
|
JSON.stringify(atmStore.context, null, 2)
|
||||||
|
}}</pre>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ onUnmounted(() => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- QR code -->
|
<!-- QR code -->
|
||||||
<div class="rounded-xl bg-qr p-6">
|
<div class="rounded-xl bg-qr p-4 lg:p-6 max-w-full [&_svg]:max-w-full [&_svg]:h-auto">
|
||||||
<QrcodeVue
|
<QrcodeVue
|
||||||
:value="value"
|
:value="value"
|
||||||
:size="qrSize"
|
:size="qrSize"
|
||||||
|
|
|
||||||
|
|
@ -624,7 +624,7 @@
|
||||||
box-shadow: 0 0 0 0 var(--glow-color, transparent);
|
box-shadow: 0 0 0 0 var(--glow-color, transparent);
|
||||||
}
|
}
|
||||||
50% {
|
50% {
|
||||||
box-shadow: 0 0 40px 8px var(--glow-color, transparent);
|
box-shadow: 0 0 20px 4px var(--glow-color, transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@utility animate-zone-glow {
|
@utility animate-zone-glow {
|
||||||
|
|
|
||||||
|
|
@ -168,24 +168,24 @@ const isProcessing = computed(() => atmStore.isPayingInvoice)
|
||||||
<div class="flex min-h-0 flex-1 flex-col">
|
<div class="flex min-h-0 flex-1 flex-col">
|
||||||
<!-- Header with success flow accent -->
|
<!-- Header with success flow accent -->
|
||||||
<header
|
<header
|
||||||
class="flex items-center px-8 py-4"
|
class="flex items-center px-4 lg:px-8 py-2 lg:py-4"
|
||||||
style="background: color-mix(in srgb, var(--success) 10%, var(--background))"
|
style="background: color-mix(in srgb, var(--success) 10%, var(--background))"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
v-if="showCancelButton"
|
v-if="showCancelButton"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="kiosk"
|
size="kiosk"
|
||||||
class="text-muted-foreground"
|
class="text-muted-foreground text-sm h-auto min-h-0 px-2 py-1 lg:text-2xl lg:h-[7vh] lg:min-h-[70px] lg:px-8 lg:py-3"
|
||||||
@click="cancel"
|
@click="cancel"
|
||||||
>
|
>
|
||||||
← Cancel
|
← Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<div v-else class="w-20"></div>
|
<div v-else class="w-16 lg:w-20"></div>
|
||||||
<div class="flex flex-1 flex-col items-center gap-2">
|
<div class="flex flex-1 flex-col items-center gap-1 lg:gap-2">
|
||||||
<h2 class="text-[2.5rem] font-semibold">Buy Bitcoin</h2>
|
<h2 class="text-xl lg:text-[2.5rem] font-semibold">Buy Bitcoin</h2>
|
||||||
<StepIndicator :steps="cashInSteps" :current-step="currentStepIndex" />
|
<StepIndicator :steps="cashInSteps" :current-step="currentStepIndex" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-20"></div>
|
<div class="w-16 lg:w-20"></div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Content area -->
|
<!-- Content area -->
|
||||||
|
|
@ -202,26 +202,32 @@ const isProcessing = computed(() => atmStore.isPayingInvoice)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Inserting Bills (split-screen) -->
|
<!-- Inserting Bills (split-screen) -->
|
||||||
<div v-else-if="nestedState === 'insertingBills'" key="insertingBills" class="flex flex-1">
|
<div
|
||||||
<!-- Left panel (52%) — amounts and controls -->
|
v-else-if="nestedState === 'insertingBills'"
|
||||||
<div class="flex w-[52%] flex-col justify-center gap-6 px-[4vw] py-6">
|
key="insertingBills"
|
||||||
|
class="flex flex-1 flex-col lg:flex-row"
|
||||||
|
>
|
||||||
|
<!-- Left panel — amounts and controls -->
|
||||||
|
<div
|
||||||
|
class="flex w-full lg:w-[52%] flex-col justify-center gap-4 lg:gap-6 px-4 lg:px-[4vw] py-4 lg:py-6"
|
||||||
|
>
|
||||||
<!-- Amount display -->
|
<!-- Amount display -->
|
||||||
<div v-if="context">
|
<div v-if="context">
|
||||||
<p class="text-[6vh] font-bold text-success leading-tight">
|
<p class="text-3xl lg:text-[6vh] font-bold text-success leading-tight">
|
||||||
{{ atmStore.fiatSymbol }}{{ (context.fiatAmount / 100).toFixed(2) }}
|
{{ atmStore.fiatSymbol }}{{ (context.fiatAmount / 100).toFixed(2) }}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-[3.5vh] font-semibold text-bitcoin">
|
<p class="text-xl lg:text-[3.5vh] font-semibold text-bitcoin">
|
||||||
{{ formatSats(context.satsAmount) }} sats
|
{{ formatSats(context.satsAmount) }} sats
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Rate -->
|
<!-- Rate -->
|
||||||
<p v-if="context" class="text-xl text-muted-foreground">
|
<p v-if="context" class="text-sm lg:text-xl text-muted-foreground">
|
||||||
{{ atmStore.fiatSymbol }}{{ formatFiatPrice(context.exchangeRate) }}/BTC
|
{{ atmStore.fiatSymbol }}{{ formatFiatPrice(context.exchangeRate) }}/BTC
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
v-if="context && atmStore.fiatCode !== 'USD' && atmStore.btcPriceUsd"
|
v-if="context && atmStore.fiatCode !== 'USD' && atmStore.btcPriceUsd"
|
||||||
class="text-lg text-muted-foreground"
|
class="text-sm lg:text-lg text-muted-foreground"
|
||||||
>
|
>
|
||||||
${{ formatFiatPrice(atmStore.btcPriceUsd) }} USD/BTC
|
${{ formatFiatPrice(atmStore.btcPriceUsd) }} USD/BTC
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -229,17 +235,19 @@ const isProcessing = computed(() => atmStore.isPayingInvoice)
|
||||||
<!-- Available balance -->
|
<!-- Available balance -->
|
||||||
<p
|
<p
|
||||||
v-if="context && context.availableBalance > 0"
|
v-if="context && context.availableBalance > 0"
|
||||||
class="text-xl"
|
class="text-sm lg:text-xl"
|
||||||
:class="balanceLimitReached ? 'text-foreground' : 'text-muted-foreground'"
|
:class="balanceLimitReached ? 'text-foreground' : 'text-muted-foreground'"
|
||||||
>
|
>
|
||||||
Available: {{ formatSats(context.availableBalance) }} sats
|
Available: {{ formatSats(context.availableBalance) }} sats
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Status -->
|
<!-- Status -->
|
||||||
<p v-if="balanceLimitReached" class="text-xl text-muted-foreground">
|
<p v-if="balanceLimitReached" class="text-sm lg:text-xl text-muted-foreground">
|
||||||
Maximum amount reached — press Done to continue
|
Maximum amount reached — press Done to continue
|
||||||
</p>
|
</p>
|
||||||
<p v-else class="text-xl text-muted-foreground">💵 Insert bills into the acceptor</p>
|
<p v-else class="text-sm lg:text-xl text-muted-foreground">
|
||||||
|
💵 Insert bills into the acceptor
|
||||||
|
</p>
|
||||||
|
|
||||||
<!-- Dev: Simulate bill insertion -->
|
<!-- Dev: Simulate bill insertion -->
|
||||||
<Alert v-if="atmStore.debugMode" class="border-destructive/50 bg-destructive/10">
|
<Alert v-if="atmStore.debugMode" class="border-destructive/50 bg-destructive/10">
|
||||||
|
|
@ -272,8 +280,10 @@ const isProcessing = computed(() => atmStore.isPayingInvoice)
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right panel (48%) — bill visual -->
|
<!-- Right panel — bill visual (kiosk only) -->
|
||||||
<div class="flex w-[48%] flex-col items-center justify-center gap-6 bg-foreground/5">
|
<div
|
||||||
|
class="hidden lg:flex w-[48%] flex-col items-center justify-center gap-6 bg-foreground/5"
|
||||||
|
>
|
||||||
<span class="text-[14vh] leading-none">💵</span>
|
<span class="text-[14vh] leading-none">💵</span>
|
||||||
<p class="text-2xl text-muted-foreground">Feed bills here</p>
|
<p class="text-2xl text-muted-foreground">Feed bills here</p>
|
||||||
<p v-if="context" class="text-lg text-muted-foreground">
|
<p v-if="context" class="text-lg text-muted-foreground">
|
||||||
|
|
@ -296,21 +306,29 @@ const isProcessing = computed(() => atmStore.isPayingInvoice)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Display QR (split-screen) -->
|
<!-- Display QR (split-screen) -->
|
||||||
<div v-else-if="nestedState === 'displayingQR'" key="displayingQR" class="flex flex-1">
|
<div
|
||||||
<!-- Left panel (52%) — info -->
|
v-else-if="nestedState === 'displayingQR'"
|
||||||
<div class="flex w-[52%] flex-col items-center justify-center gap-5 px-[4vw]">
|
key="displayingQR"
|
||||||
<p class="text-[2rem] font-semibold text-success">Scan to receive your sats</p>
|
class="flex flex-1 flex-col-reverse lg:flex-row"
|
||||||
<p class="text-[7vh] font-bold text-bitcoin leading-tight">
|
>
|
||||||
|
<!-- Left panel — info -->
|
||||||
|
<div
|
||||||
|
class="flex w-full lg:w-[52%] flex-col items-center justify-center gap-3 lg:gap-5 px-4 lg:px-[4vw] py-4 lg:py-0"
|
||||||
|
>
|
||||||
|
<p class="text-lg lg:text-[2rem] font-semibold text-success">
|
||||||
|
Scan to receive your sats
|
||||||
|
</p>
|
||||||
|
<p class="text-3xl lg:text-[7vh] font-bold text-bitcoin leading-tight">
|
||||||
{{ formatSats(context?.satsAmount || 0) }} sats
|
{{ formatSats(context?.satsAmount || 0) }} sats
|
||||||
</p>
|
</p>
|
||||||
<p class="text-2xl text-muted-foreground">
|
<p class="text-lg lg:text-2xl text-muted-foreground">
|
||||||
{{ atmStore.fiatSymbol }}{{ ((context?.fiatAmount || 0) / 100).toFixed(2) }}
|
{{ atmStore.fiatSymbol }}{{ ((context?.fiatAmount || 0) / 100).toFixed(2) }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Waiting indicator -->
|
<!-- Waiting indicator -->
|
||||||
<div class="flex items-center gap-3 pt-4">
|
<div class="flex items-center gap-3 pt-2 lg:pt-4">
|
||||||
<BounceDots />
|
<BounceDots />
|
||||||
<p class="text-xl text-muted-foreground">Waiting for wallet scan...</p>
|
<p class="text-sm lg:text-xl text-muted-foreground">Waiting for wallet scan...</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- LNURL URI (web-ui only) -->
|
<!-- LNURL URI (web-ui only) -->
|
||||||
|
|
@ -342,8 +360,10 @@ const isProcessing = computed(() => atmStore.isPayingInvoice)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right panel (48%) — QR code -->
|
<!-- Right panel — QR code -->
|
||||||
<div class="flex w-[48%] flex-col items-center justify-center bg-foreground/5 p-6">
|
<div
|
||||||
|
class="flex w-full lg:w-[48%] flex-col items-center justify-center bg-foreground/5 p-4 lg:p-6"
|
||||||
|
>
|
||||||
<div v-if="isGeneratingLnurl && !context?.ndebitUri">
|
<div v-if="isGeneratingLnurl && !context?.ndebitUri">
|
||||||
<BounceDots class="scale-150" />
|
<BounceDots class="scale-150" />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -364,9 +384,9 @@ const isProcessing = computed(() => atmStore.isPayingInvoice)
|
||||||
class="flex flex-1 flex-col items-center justify-center gap-6 p-8"
|
class="flex flex-1 flex-col items-center justify-center gap-6 p-8"
|
||||||
style="background: color-mix(in srgb, var(--destructive) 8%, var(--background))"
|
style="background: color-mix(in srgb, var(--destructive) 8%, var(--background))"
|
||||||
>
|
>
|
||||||
<div class="text-[8vh]">⚠️</div>
|
<div class="text-5xl lg:text-[8vh]">⚠️</div>
|
||||||
<h3 class="text-[3rem] font-bold text-destructive">Cash Already Accepted</h3>
|
<h3 class="text-2xl lg:text-[3rem] font-bold text-destructive">Cash Already Accepted</h3>
|
||||||
<p class="max-w-lg text-center text-2xl text-muted-foreground">
|
<p class="max-w-lg text-center text-base lg:text-2xl text-muted-foreground">
|
||||||
Your {{ atmStore.fiatSymbol
|
Your {{ atmStore.fiatSymbol
|
||||||
}}{{ context ? (context.fiatAmount / 100).toLocaleString() : '' }} has been accepted and
|
}}{{ context ? (context.fiatAmount / 100).toLocaleString() : '' }} has been accepted and
|
||||||
cannot be returned. Please scan the QR code to receive your Bitcoin.
|
cannot be returned. Please scan the QR code to receive your Bitcoin.
|
||||||
|
|
@ -392,32 +412,32 @@ const isProcessing = computed(() => atmStore.isPayingInvoice)
|
||||||
<div
|
<div
|
||||||
v-else-if="nestedState === 'complete'"
|
v-else-if="nestedState === 'complete'"
|
||||||
key="complete"
|
key="complete"
|
||||||
class="flex flex-1 items-center justify-center gap-10 p-8"
|
class="flex flex-1 flex-col lg:flex-row items-center justify-center gap-6 lg:gap-10 p-4 lg:p-8"
|
||||||
style="background: color-mix(in srgb, var(--success) 10%, var(--background))"
|
style="background: color-mix(in srgb, var(--success) 10%, var(--background))"
|
||||||
>
|
>
|
||||||
<!-- Left side — details -->
|
<!-- Left side — details -->
|
||||||
<div class="flex flex-col items-center gap-5">
|
<div class="flex flex-col items-center gap-3 lg:gap-5">
|
||||||
<div class="text-[8vh]">🎉</div>
|
<div class="text-5xl lg:text-[8vh]">🎉</div>
|
||||||
<h3 class="text-[3rem] font-bold text-success">Transaction Complete!</h3>
|
<h3 class="text-2xl lg:text-[3rem] font-bold text-success">Transaction Complete!</h3>
|
||||||
|
|
||||||
<!-- Transaction details -->
|
<!-- Transaction details -->
|
||||||
<div
|
<div
|
||||||
v-if="context"
|
v-if="context"
|
||||||
class="w-full max-w-md rounded-xl bg-background/60 px-8 py-6 space-y-3"
|
class="w-full max-w-md rounded-xl bg-background/60 px-4 py-4 lg:px-8 lg:py-6 space-y-2 lg:space-y-3"
|
||||||
>
|
>
|
||||||
<div class="flex justify-between text-2xl">
|
<div class="flex justify-between text-base lg:text-2xl">
|
||||||
<span class="text-muted-foreground">Inserted</span>
|
<span class="text-muted-foreground">Inserted</span>
|
||||||
<span class="font-semibold"
|
<span class="font-semibold"
|
||||||
>{{ atmStore.fiatSymbol }}{{ (context.fiatAmount / 100).toFixed(2) }}</span
|
>{{ atmStore.fiatSymbol }}{{ (context.fiatAmount / 100).toFixed(2) }}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between text-2xl">
|
<div class="flex justify-between text-base lg:text-2xl">
|
||||||
<span class="text-muted-foreground">Rate</span>
|
<span class="text-muted-foreground">Rate</span>
|
||||||
<span
|
<span
|
||||||
>{{ atmStore.fiatSymbol }}{{ formatFiatPrice(context.exchangeRate) }}/BTC</span
|
>{{ atmStore.fiatSymbol }}{{ formatFiatPrice(context.exchangeRate) }}/BTC</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between text-2xl">
|
<div class="flex justify-between text-base lg:text-2xl">
|
||||||
<span class="text-muted-foreground"
|
<span class="text-muted-foreground"
|
||||||
>Commission ({{ (context.feePercent * 100).toFixed(1) }}%)</span
|
>Commission ({{ (context.feePercent * 100).toFixed(1) }}%)</span
|
||||||
>
|
>
|
||||||
|
|
@ -434,13 +454,13 @@ const isProcessing = computed(() => atmStore.isPayingInvoice)
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="border-t border-border my-2"></div>
|
<div class="border-t border-border my-2"></div>
|
||||||
<div class="flex justify-between text-[1.8rem] font-bold">
|
<div class="flex justify-between text-xl lg:text-[1.8rem] font-bold">
|
||||||
<span class="text-success">You received</span>
|
<span class="text-success">You received</span>
|
||||||
<span class="text-success">{{ formatSats(context.satsAmount) }} sats</span>
|
<span class="text-success">{{ formatSats(context.satsAmount) }} sats</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-xl text-muted-foreground">
|
<p class="text-sm lg:text-xl text-muted-foreground">
|
||||||
Take a photo of this screen for your receipt
|
Take a photo of this screen for your receipt
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -462,12 +482,14 @@ const isProcessing = computed(() => atmStore.isPayingInvoice)
|
||||||
<div
|
<div
|
||||||
v-else-if="nestedState === 'error'"
|
v-else-if="nestedState === 'error'"
|
||||||
key="error"
|
key="error"
|
||||||
class="flex flex-1 flex-col items-center justify-center gap-6 p-8"
|
class="flex flex-1 flex-col items-center justify-center gap-4 lg:gap-6 p-4 lg:p-8"
|
||||||
style="background: color-mix(in srgb, var(--destructive) 8%, var(--background))"
|
style="background: color-mix(in srgb, var(--destructive) 8%, var(--background))"
|
||||||
>
|
>
|
||||||
<div class="text-[8vh]">⚠️</div>
|
<div class="text-5xl lg:text-[8vh]">⚠️</div>
|
||||||
<h3 class="text-[3rem] font-bold text-destructive">Error</h3>
|
<h3 class="text-2xl lg:text-[3rem] font-bold text-destructive">Error</h3>
|
||||||
<p class="text-2xl text-muted-foreground">{{ context?.error || 'An error occurred' }}</p>
|
<p class="text-base lg:text-2xl text-muted-foreground">
|
||||||
|
{{ context?.error || 'An error occurred' }}
|
||||||
|
</p>
|
||||||
<Button
|
<Button
|
||||||
v-if="context && context.billsInserted.length > 0"
|
v-if="context && context.billsInserted.length > 0"
|
||||||
class="w-full max-w-md bg-success text-success-foreground"
|
class="w-full max-w-md bg-success text-success-foreground"
|
||||||
|
|
|
||||||
|
|
@ -117,17 +117,21 @@ function formatFiat(cents: number): string {
|
||||||
<div class="flex min-h-0 flex-1 flex-col">
|
<div class="flex min-h-0 flex-1 flex-col">
|
||||||
<!-- Header with warning flow accent -->
|
<!-- Header with warning flow accent -->
|
||||||
<header
|
<header
|
||||||
class="flex items-center px-8 py-4"
|
class="flex items-center px-4 lg:px-8 py-2 lg:py-4"
|
||||||
style="background: color-mix(in srgb, var(--warning) 10%, var(--background))"
|
style="background: color-mix(in srgb, var(--warning) 10%, var(--background))"
|
||||||
>
|
>
|
||||||
<Button variant="ghost" size="kiosk" class="text-muted-foreground" @click="cancel"
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="kiosk"
|
||||||
|
class="text-muted-foreground text-sm h-auto min-h-0 px-2 py-1 lg:text-2xl lg:h-[7vh] lg:min-h-[70px] lg:px-8 lg:py-3"
|
||||||
|
@click="cancel"
|
||||||
>← Cancel</Button
|
>← Cancel</Button
|
||||||
>
|
>
|
||||||
<div class="flex flex-1 flex-col items-center gap-2">
|
<div class="flex flex-1 flex-col items-center gap-1 lg:gap-2">
|
||||||
<h2 class="text-[2.5rem] font-semibold">Sell Bitcoin</h2>
|
<h2 class="text-xl lg:text-[2.5rem] font-semibold">Sell Bitcoin</h2>
|
||||||
<StepIndicator :steps="cashOutSteps" :current-step="currentStepIndex" />
|
<StepIndicator :steps="cashOutSteps" :current-step="currentStepIndex" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-20"></div>
|
<div class="w-16 lg:w-20"></div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Content area -->
|
<!-- Content area -->
|
||||||
|
|
@ -147,16 +151,16 @@ function formatFiat(cents: number): string {
|
||||||
<div
|
<div
|
||||||
v-else-if="nestedState === 'selectingAmount'"
|
v-else-if="nestedState === 'selectingAmount'"
|
||||||
key="selectingAmount"
|
key="selectingAmount"
|
||||||
class="flex flex-1 flex-col justify-center px-[8vw] py-6 gap-6"
|
class="flex flex-1 flex-col justify-center px-4 lg:px-[8vw] py-4 lg:py-6 gap-4 lg:gap-6"
|
||||||
>
|
>
|
||||||
<!-- Denomination grid -->
|
<!-- Denomination grid -->
|
||||||
<div class="grid grid-cols-2 gap-6">
|
<div class="grid grid-cols-2 gap-3 lg:gap-6">
|
||||||
<div
|
<div
|
||||||
v-for="denom in availableDenominations"
|
v-for="denom in availableDenominations"
|
||||||
:key="denom"
|
:key="denom"
|
||||||
class="flex items-center justify-between rounded-lg bg-card px-6 py-5"
|
class="flex items-center justify-between rounded-lg bg-card px-3 py-3 lg:px-6 lg:py-5"
|
||||||
>
|
>
|
||||||
<span class="text-[2.5rem] font-bold text-success"
|
<span class="text-xl lg:text-[2.5rem] font-bold text-success"
|
||||||
>{{ atmStore.fiatSymbol }}{{ denom }}</span
|
>{{ atmStore.fiatSymbol }}{{ denom }}</span
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
|
|
@ -168,7 +172,7 @@ function formatFiat(cents: number): string {
|
||||||
>
|
>
|
||||||
-
|
-
|
||||||
</Button>
|
</Button>
|
||||||
<span class="w-16 text-center text-[2rem] font-medium">{{
|
<span class="w-10 lg:w-16 text-center text-lg lg:text-[2rem] font-medium">{{
|
||||||
selectionCounts[denom] || 0
|
selectionCounts[denom] || 0
|
||||||
}}</span>
|
}}</span>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -184,18 +188,18 @@ function formatFiat(cents: number): string {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Amount summary -->
|
<!-- Amount summary -->
|
||||||
<div class="text-center py-4">
|
<div class="text-center py-2 lg:py-4">
|
||||||
<p class="text-xl text-muted-foreground">You will receive</p>
|
<p class="text-sm lg:text-xl text-muted-foreground">You will receive</p>
|
||||||
<p class="text-[7vh] font-bold text-success leading-tight">
|
<p class="text-4xl lg:text-[7vh] font-bold text-success leading-tight">
|
||||||
{{ atmStore.fiatSymbol }}{{ context ? formatFiat(context.fiatAmount) : '0.00' }}
|
{{ atmStore.fiatSymbol }}{{ context ? formatFiat(context.fiatAmount) : '0.00' }}
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-2 text-2xl text-muted-foreground">
|
<p class="mt-1 lg:mt-2 text-lg lg:text-2xl text-muted-foreground">
|
||||||
You pay:
|
You pay:
|
||||||
<Badge variant="secondary" class="ml-1 text-2xl">
|
<Badge variant="secondary" class="ml-1 text-lg lg:text-2xl">
|
||||||
{{ context ? formatSats(context.satsAmount) : 0 }} sats
|
{{ context ? formatSats(context.satsAmount) : 0 }} sats
|
||||||
</Badge>
|
</Badge>
|
||||||
</p>
|
</p>
|
||||||
<p v-if="context" class="mt-1 text-lg text-muted-foreground">
|
<p v-if="context" class="mt-1 text-sm lg:text-lg text-muted-foreground">
|
||||||
Rate:
|
Rate:
|
||||||
<Badge variant="secondary" class="text-lg">
|
<Badge variant="secondary" class="text-lg">
|
||||||
{{ atmStore.fiatSymbol }}{{ formatFiatPrice(context.exchangeRate) }}/BTC
|
{{ atmStore.fiatSymbol }}{{ formatFiatPrice(context.exchangeRate) }}/BTC
|
||||||
|
|
@ -233,7 +237,7 @@ function formatFiat(cents: number): string {
|
||||||
<!-- Instructions -->
|
<!-- Instructions -->
|
||||||
<Alert class="border-bitcoin/50 bg-bitcoin/10">
|
<Alert class="border-bitcoin/50 bg-bitcoin/10">
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
<p class="text-xl text-muted-foreground">
|
<p class="text-sm lg:text-xl text-muted-foreground">
|
||||||
Select the cash amount you want to receive. You'll pay the equivalent in Bitcoin.
|
Select the cash amount you want to receive. You'll pay the equivalent in Bitcoin.
|
||||||
</p>
|
</p>
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
|
|
@ -254,25 +258,27 @@ function formatFiat(cents: number): string {
|
||||||
<div
|
<div
|
||||||
v-else-if="nestedState === 'displayingInvoice'"
|
v-else-if="nestedState === 'displayingInvoice'"
|
||||||
key="displayingInvoice"
|
key="displayingInvoice"
|
||||||
class="flex flex-1"
|
class="flex flex-1 flex-col-reverse lg:flex-row"
|
||||||
>
|
>
|
||||||
<!-- Left panel (52%) — info -->
|
<!-- Left panel — info -->
|
||||||
<div class="flex w-[52%] flex-col items-center justify-center gap-5 px-[4vw]">
|
<div
|
||||||
<p class="text-[2rem] font-semibold text-warning">Scan to Pay</p>
|
class="flex w-full lg:w-[52%] flex-col items-center justify-center gap-3 lg:gap-5 px-4 lg:px-[4vw] py-4 lg:py-0"
|
||||||
<p class="text-[7vh] font-bold text-bitcoin leading-tight">
|
>
|
||||||
|
<p class="text-lg lg:text-[2rem] font-semibold text-warning">Scan to Pay</p>
|
||||||
|
<p class="text-3xl lg:text-[7vh] font-bold text-bitcoin leading-tight">
|
||||||
{{ context ? formatSats(context.satsAmount) : 0 }} sats
|
{{ context ? formatSats(context.satsAmount) : 0 }} sats
|
||||||
</p>
|
</p>
|
||||||
<p class="text-2xl text-muted-foreground">
|
<p class="text-lg lg:text-2xl text-muted-foreground">
|
||||||
to receive
|
to receive
|
||||||
<Badge class="bg-success text-success-foreground text-2xl">
|
<Badge class="bg-success text-success-foreground text-lg lg:text-2xl">
|
||||||
{{ atmStore.fiatSymbol }}{{ context ? formatFiat(context.fiatAmount) : '0.00' }}
|
{{ atmStore.fiatSymbol }}{{ context ? formatFiat(context.fiatAmount) : '0.00' }}
|
||||||
</Badge>
|
</Badge>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Waiting indicator -->
|
<!-- Waiting indicator -->
|
||||||
<div class="flex items-center gap-3 pt-4">
|
<div class="flex items-center gap-3 pt-2 lg:pt-4">
|
||||||
<BounceDots />
|
<BounceDots />
|
||||||
<p class="text-xl text-muted-foreground">Waiting for payment...</p>
|
<p class="text-sm lg:text-xl text-muted-foreground">Waiting for payment...</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Invoice info with copy button (web-ui only) -->
|
<!-- Invoice info with copy button (web-ui only) -->
|
||||||
|
|
@ -300,8 +306,10 @@ function formatFiat(cents: number): string {
|
||||||
</Alert>
|
</Alert>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right panel (48%) — QR code -->
|
<!-- Right panel — QR code -->
|
||||||
<div class="flex w-[48%] flex-col items-center justify-center bg-foreground/5 p-6">
|
<div
|
||||||
|
class="flex w-full lg:w-[48%] flex-col items-center justify-center bg-foreground/5 p-4 lg:p-6"
|
||||||
|
>
|
||||||
<div v-if="context?.invoice">
|
<div v-if="context?.invoice">
|
||||||
<QRCode :value="context.invoice" :size="380" />
|
<QRCode :value="context.invoice" :size="380" />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -330,9 +338,9 @@ function formatFiat(cents: number): string {
|
||||||
class="flex flex-1 flex-col items-center justify-center gap-6 p-8"
|
class="flex flex-1 flex-col items-center justify-center gap-6 p-8"
|
||||||
style="background: color-mix(in srgb, var(--success) 10%, var(--background))"
|
style="background: color-mix(in srgb, var(--success) 10%, var(--background))"
|
||||||
>
|
>
|
||||||
<div class="text-[8vh] text-success">✅</div>
|
<div class="text-5xl lg:text-[8vh] text-success">✅</div>
|
||||||
<h3 class="text-[3rem] font-bold text-success">Cash Ready!</h3>
|
<h3 class="text-2xl lg:text-[3rem] font-bold text-success">Cash Ready!</h3>
|
||||||
<p v-if="context" class="text-2xl">
|
<p v-if="context" class="text-lg lg:text-2xl">
|
||||||
Please take your
|
Please take your
|
||||||
<Badge class="bg-success text-success-foreground text-2xl"
|
<Badge class="bg-success text-success-foreground text-2xl"
|
||||||
>{{ atmStore.fiatSymbol }}{{ formatFiat(context.fiatAmount) }}</Badge
|
>{{ atmStore.fiatSymbol }}{{ formatFiat(context.fiatAmount) }}</Badge
|
||||||
|
|
@ -351,30 +359,30 @@ function formatFiat(cents: number): string {
|
||||||
<div
|
<div
|
||||||
v-else-if="nestedState === 'complete'"
|
v-else-if="nestedState === 'complete'"
|
||||||
key="complete"
|
key="complete"
|
||||||
class="flex flex-1 items-center justify-center gap-10 p-8"
|
class="flex flex-1 flex-col lg:flex-row items-center justify-center gap-6 lg:gap-10 p-4 lg:p-8"
|
||||||
style="background: color-mix(in srgb, var(--success) 10%, var(--background))"
|
style="background: color-mix(in srgb, var(--success) 10%, var(--background))"
|
||||||
>
|
>
|
||||||
<!-- Left side — details -->
|
<!-- Left side — details -->
|
||||||
<div class="flex flex-col items-center gap-5">
|
<div class="flex flex-col items-center gap-3 lg:gap-5">
|
||||||
<div class="text-[8vh]">🎉</div>
|
<div class="text-5xl lg:text-[8vh]">🎉</div>
|
||||||
<h3 class="text-[3rem] font-bold text-success">Transaction Complete!</h3>
|
<h3 class="text-2xl lg:text-[3rem] font-bold text-success">Transaction Complete!</h3>
|
||||||
|
|
||||||
<!-- Transaction details -->
|
<!-- Transaction details -->
|
||||||
<div
|
<div
|
||||||
v-if="context"
|
v-if="context"
|
||||||
class="w-full max-w-md rounded-xl bg-background/60 px-8 py-6 space-y-3"
|
class="w-full max-w-md rounded-xl bg-background/60 px-4 py-4 lg:px-8 lg:py-6 space-y-2 lg:space-y-3"
|
||||||
>
|
>
|
||||||
<div class="flex justify-between text-2xl">
|
<div class="flex justify-between text-base lg:text-2xl">
|
||||||
<span class="text-muted-foreground">You paid</span>
|
<span class="text-muted-foreground">You paid</span>
|
||||||
<span class="font-semibold">{{ formatSats(context.satsAmount) }} sats</span>
|
<span class="font-semibold">{{ formatSats(context.satsAmount) }} sats</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between text-2xl">
|
<div class="flex justify-between text-base lg:text-2xl">
|
||||||
<span class="text-muted-foreground">Rate</span>
|
<span class="text-muted-foreground">Rate</span>
|
||||||
<span
|
<span
|
||||||
>{{ atmStore.fiatSymbol }}{{ formatFiatPrice(context.exchangeRate) }}/BTC</span
|
>{{ atmStore.fiatSymbol }}{{ formatFiatPrice(context.exchangeRate) }}/BTC</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between text-2xl">
|
<div class="flex justify-between text-base lg:text-2xl">
|
||||||
<span class="text-muted-foreground"
|
<span class="text-muted-foreground"
|
||||||
>Commission ({{ (context.feePercent * 100).toFixed(1) }}%)</span
|
>Commission ({{ (context.feePercent * 100).toFixed(1) }}%)</span
|
||||||
>
|
>
|
||||||
|
|
@ -391,7 +399,7 @@ function formatFiat(cents: number): string {
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="border-t border-border my-2"></div>
|
<div class="border-t border-border my-2"></div>
|
||||||
<div class="flex justify-between text-[1.8rem] font-bold">
|
<div class="flex justify-between text-xl lg:text-[1.8rem] font-bold">
|
||||||
<span class="text-success">Cash received</span>
|
<span class="text-success">Cash received</span>
|
||||||
<span class="text-success"
|
<span class="text-success"
|
||||||
>{{ atmStore.fiatSymbol }}{{ formatFiat(context.fiatAmount) }}</span
|
>{{ atmStore.fiatSymbol }}{{ formatFiat(context.fiatAmount) }}</span
|
||||||
|
|
@ -399,7 +407,7 @@ function formatFiat(cents: number): string {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-xl text-muted-foreground">
|
<p class="text-sm lg:text-xl text-muted-foreground">
|
||||||
Take a photo of this screen for your receipt
|
Take a photo of this screen for your receipt
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -421,12 +429,14 @@ function formatFiat(cents: number): string {
|
||||||
<div
|
<div
|
||||||
v-else-if="nestedState === 'error' || nestedState === 'dispenseError'"
|
v-else-if="nestedState === 'error' || nestedState === 'dispenseError'"
|
||||||
key="error"
|
key="error"
|
||||||
class="flex flex-1 flex-col items-center justify-center gap-6 p-8"
|
class="flex flex-1 flex-col items-center justify-center gap-4 lg:gap-6 p-4 lg:p-8"
|
||||||
style="background: color-mix(in srgb, var(--destructive) 8%, var(--background))"
|
style="background: color-mix(in srgb, var(--destructive) 8%, var(--background))"
|
||||||
>
|
>
|
||||||
<div class="text-[8vh]">⚠️</div>
|
<div class="text-5xl lg:text-[8vh]">⚠️</div>
|
||||||
<h3 class="text-[3rem] font-bold text-destructive">Error</h3>
|
<h3 class="text-2xl lg:text-[3rem] font-bold text-destructive">Error</h3>
|
||||||
<p class="text-2xl text-muted-foreground">{{ context?.error || 'An error occurred' }}</p>
|
<p class="text-base lg:text-2xl text-muted-foreground">
|
||||||
|
{{ context?.error || 'An error occurred' }}
|
||||||
|
</p>
|
||||||
<Button
|
<Button
|
||||||
class="bg-gradient-to-r from-orange-500 to-yellow-400 text-black"
|
class="bg-gradient-to-r from-orange-500 to-yellow-400 text-black"
|
||||||
size="kiosk"
|
size="kiosk"
|
||||||
|
|
|
||||||
|
|
@ -59,23 +59,31 @@ function handleCashOut() {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex min-h-0 flex-1 flex-col">
|
<div class="flex min-h-0 flex-1 flex-col justify-around lg:justify-start">
|
||||||
<!-- Brand Zone (top ~30%) -->
|
<!-- Brand Zone -->
|
||||||
<div class="flex flex-col items-center justify-center gap-2 pt-[4vh] pb-[2vh]">
|
<div
|
||||||
|
class="flex flex-col items-center justify-center gap-1.5 lg:gap-2 pt-6 lg:pt-[4vh] pb-2 lg:pb-[2vh]"
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src="/logo.png"
|
src="/logo.png"
|
||||||
alt="Lamassu"
|
alt="Lamassu"
|
||||||
class="h-[12vh] w-[12vh] animate-float drop-shadow-[0_0_24px_var(--primary)]"
|
class="h-20 w-20 lg:h-[12vh] lg:w-[12vh] animate-float drop-shadow-[0_0_24px_var(--primary)]"
|
||||||
/>
|
/>
|
||||||
<h1 class="text-[5vh] font-bold text-primary leading-tight">Bitcoinmat</h1>
|
<h1 class="text-3xl lg:text-[5vh] font-bold text-primary leading-tight">Bitcoinmat</h1>
|
||||||
<div class="flex flex-wrap items-center justify-center gap-3">
|
<div class="flex flex-wrap items-center justify-center gap-2 lg:gap-3">
|
||||||
<Badge variant="outline" class="border-success text-success text-lg">No KYC</Badge>
|
<Badge variant="outline" class="border-success text-success text-[11px] lg:text-lg"
|
||||||
<Badge variant="outline" class="border-primary text-primary text-lg">No Registration</Badge>
|
>No KYC</Badge
|
||||||
<Badge variant="outline" class="border-bitcoin text-bitcoin text-lg">Just Bitcoin</Badge>
|
>
|
||||||
|
<Badge variant="outline" class="border-primary text-primary text-[11px] lg:text-lg"
|
||||||
|
>No Registration</Badge
|
||||||
|
>
|
||||||
|
<Badge variant="outline" class="border-bitcoin text-bitcoin text-[11px] lg:text-lg"
|
||||||
|
>Just Bitcoin</Badge
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<!-- Balance + Commission rates -->
|
<!-- Balance + Commission rates -->
|
||||||
<div
|
<div
|
||||||
class="mt-1 flex flex-wrap items-center justify-center gap-4 text-[2vh] text-muted-foreground"
|
class="mt-1 flex flex-wrap items-center justify-center gap-2 lg:gap-4 text-[11px] lg:text-[2vh] text-muted-foreground"
|
||||||
>
|
>
|
||||||
<span v-if="atmStore.balanceSats !== null">
|
<span v-if="atmStore.balanceSats !== null">
|
||||||
Available:
|
Available:
|
||||||
|
|
@ -106,36 +114,39 @@ function handleCashOut() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Touch Zones (fills remaining space) -->
|
<!-- Touch Zones -->
|
||||||
<div class="flex flex-1 items-center justify-center gap-[8vw] pb-[4vh]">
|
<div
|
||||||
|
class="flex flex-col lg:flex-1 lg:flex-row items-center justify-center gap-5 lg:gap-[8vw] lg:pb-[4vh]"
|
||||||
|
>
|
||||||
<!-- Buy Bitcoin -->
|
<!-- Buy Bitcoin -->
|
||||||
<button
|
<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"
|
class="flex aspect-square w-40 lg:w-[36vh] flex-col items-center justify-center gap-1.5 lg: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)"
|
style="--glow-color: var(--bitcoin)"
|
||||||
@click="handleCashIn"
|
@click="handleCashIn"
|
||||||
>
|
>
|
||||||
<BitcoinIcon :size="100" class="shrink-0" />
|
<BitcoinIcon :size="100" class="shrink-0 w-10 h-10 lg:w-[100px] lg:h-[100px]" />
|
||||||
<span class="text-[3.5vh] font-bold text-bitcoin">Buy Bitcoin</span>
|
<span class="text-base lg:text-[3.5vh] font-bold text-bitcoin">Buy Bitcoin</span>
|
||||||
<span class="text-[1.8vh] text-foreground/70">Insert cash, receive sats</span>
|
<span class="text-[10px] lg:text-[1.8vh] text-foreground/70"
|
||||||
|
>Insert cash, receive sats</span
|
||||||
|
>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Sell Bitcoin -->
|
<!-- Sell Bitcoin -->
|
||||||
<button
|
<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"
|
class="flex aspect-square w-40 lg:w-[36vh] flex-col items-center justify-center gap-1.5 lg: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)"
|
style="--glow-color: var(--success)"
|
||||||
@click="handleCashOut"
|
@click="handleCashOut"
|
||||||
>
|
>
|
||||||
<span class="text-[8vh] leading-none">💵</span>
|
<span class="text-4xl lg:text-[8vh] leading-none">💵</span>
|
||||||
<span class="text-[3.5vh] font-bold text-success">Sell Bitcoin</span>
|
<span class="text-base lg:text-[3.5vh] font-bold text-success">Sell Bitcoin</span>
|
||||||
<span class="text-[1.8vh] text-foreground/70">Pay invoice, receive cash</span>
|
<span class="text-[10px] lg:text-[1.8vh] text-foreground/70"
|
||||||
|
>Pay invoice, receive cash</span
|
||||||
|
>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Dev QR buttons -->
|
<!-- Dev QR buttons -->
|
||||||
<div
|
<div v-if="atmStore.debugMode" class="flex flex-wrap items-center justify-center gap-2 pb-2">
|
||||||
v-if="atmStore.debugMode"
|
|
||||||
class="absolute bottom-16 left-1/2 -translate-x-1/2 flex flex-wrap items-center justify-center gap-4"
|
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
v-if="lndconnectUrl"
|
v-if="lndconnectUrl"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|
@ -207,7 +218,7 @@ function handleCashOut() {
|
||||||
v-if="!atmStore.debugMode && atmStore.allowMockFallback"
|
v-if="!atmStore.debugMode && atmStore.allowMockFallback"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="kiosk"
|
size="kiosk"
|
||||||
class="absolute bottom-6 right-6 text-lg text-muted-foreground"
|
class="absolute bottom-2 right-2 lg:bottom-6 lg:right-6 h-8 min-h-0 px-2 text-xs lg:h-[7vh] lg:min-h-[70px] lg:px-8 lg:text-2xl text-muted-foreground"
|
||||||
@click="atmStore.toggleDebug"
|
@click="atmStore.toggleDebug"
|
||||||
>
|
>
|
||||||
Show Debug
|
Show Debug
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue