feat(machine): add 6 switchable color themes
Add theme system with Gruvbox (default), Catppuccin, Dracula, Nord, Tokyo Night, and Cyberpunk palettes — each with light/dark variants. Theme picker on idle screen and debug overlay, persisted to localStorage. Also fixes @custom-variant dark to use :where() per Tailwind v4 best practice, and replaces Buy Bitcoin emoji with BitcoinIcon component. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
55ea9f8175
commit
b0fc5cf585
4 changed files with 452 additions and 2 deletions
|
|
@ -1,10 +1,12 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
import { useAtmStore } from '@/stores/atm'
|
import { useAtmStore } from '@/stores/atm'
|
||||||
|
import { useTheme } from '@/composables/useTheme'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
|
|
||||||
const atmStore = useAtmStore()
|
const atmStore = useAtmStore()
|
||||||
|
const { current: currentTheme, themes } = useTheme()
|
||||||
|
|
||||||
function formatSats(sats: number): string {
|
function formatSats(sats: number): string {
|
||||||
return sats.toLocaleString()
|
return sats.toLocaleString()
|
||||||
|
|
@ -68,6 +70,19 @@ function toggleLiveServices() {
|
||||||
<Button variant="outline" size="sm" class="h-6 text-xs" @click="toggleLiveServices">
|
<Button variant="outline" size="sm" class="h-6 text-xs" @click="toggleLiveServices">
|
||||||
{{ atmStore.useLiveServices ? 'Switch to Mock' : 'Connect Live' }}
|
{{ atmStore.useLiveServices ? 'Switch to Mock' : 'Connect Live' }}
|
||||||
</Button>
|
</Button>
|
||||||
|
<span class="text-muted-foreground">|</span>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<Button
|
||||||
|
v-for="theme in themes"
|
||||||
|
:key="theme.id"
|
||||||
|
:variant="currentTheme === theme.id ? 'default' : 'outline'"
|
||||||
|
size="sm"
|
||||||
|
class="h-6 text-xs"
|
||||||
|
@click="currentTheme = theme.id"
|
||||||
|
>
|
||||||
|
{{ theme.label }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<pre class="text-muted-foreground">{{ JSON.stringify(atmStore.context, null, 2) }}</pre>
|
<pre class="text-muted-foreground">{{ JSON.stringify(atmStore.context, null, 2) }}</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
62
apps/machine/src/composables/useTheme.ts
Normal file
62
apps/machine/src/composables/useTheme.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
|
export type ThemeId = 'gruvbox' | 'catppuccin' | 'cyberpunk' | 'dracula' | 'nord' | 'tokyo-night'
|
||||||
|
|
||||||
|
export interface ThemeOption {
|
||||||
|
id: ThemeId
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const themes: ThemeOption[] = [
|
||||||
|
{ id: 'gruvbox', label: 'Gruvbox' },
|
||||||
|
{ id: 'catppuccin', label: 'Catppuccin' },
|
||||||
|
{ id: 'dracula', label: 'Dracula' },
|
||||||
|
{ id: 'nord', label: 'Nord' },
|
||||||
|
{ id: 'tokyo-night', label: 'Tokyo Night' },
|
||||||
|
{ id: 'cyberpunk', label: 'Cyberpunk' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'lamassu-theme'
|
||||||
|
|
||||||
|
const current = ref<ThemeId>(loadTheme())
|
||||||
|
|
||||||
|
function loadTheme(): ThemeId {
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(STORAGE_KEY)
|
||||||
|
if (stored && themes.some((t) => t.id === stored)) {
|
||||||
|
return stored as ThemeId
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// localStorage unavailable (e.g. SSR)
|
||||||
|
}
|
||||||
|
return 'gruvbox'
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTheme(id: ThemeId) {
|
||||||
|
const el = document.documentElement
|
||||||
|
if (id === 'gruvbox') {
|
||||||
|
el.removeAttribute('data-theme')
|
||||||
|
} else {
|
||||||
|
el.setAttribute('data-theme', id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply on load
|
||||||
|
applyTheme(current.value)
|
||||||
|
|
||||||
|
// Persist and apply on change
|
||||||
|
watch(current, (id) => {
|
||||||
|
applyTheme(id)
|
||||||
|
try {
|
||||||
|
localStorage.setItem(STORAGE_KEY, id)
|
||||||
|
} catch {
|
||||||
|
// localStorage unavailable
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export function useTheme() {
|
||||||
|
return {
|
||||||
|
current,
|
||||||
|
themes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
@import 'tailwindcss';
|
@import 'tailwindcss';
|
||||||
@import 'tw-animate-css';
|
@import 'tw-animate-css';
|
||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:where(.dark, .dark *));
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
--radius-sm: calc(var(--radius) - 4px);
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
|
|
@ -113,6 +113,361 @@
|
||||||
--qr-foreground: #3c3836; /* gruvbox fg - maintains contrast */
|
--qr-foreground: #3c3836; /* gruvbox fg - maintains contrast */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Catppuccin Palette
|
||||||
|
* https://github.com/catppuccin/catppuccin
|
||||||
|
*/
|
||||||
|
|
||||||
|
[data-theme='catppuccin'] {
|
||||||
|
--radius: 0.625rem;
|
||||||
|
|
||||||
|
/* Catppuccin Latte (light) */
|
||||||
|
--background: #eff1f5; /* base */
|
||||||
|
--foreground: #4c4f69; /* text */
|
||||||
|
--card: #e6e9ef; /* mantle */
|
||||||
|
--card-foreground: #4c4f69;
|
||||||
|
--popover: #e6e9ef;
|
||||||
|
--popover-foreground: #4c4f69;
|
||||||
|
--primary: #8839ef; /* mauve */
|
||||||
|
--primary-foreground: #eff1f5;
|
||||||
|
--secondary: #ccd0da; /* surface0 */
|
||||||
|
--secondary-foreground: #4c4f69;
|
||||||
|
--muted: #ccd0da;
|
||||||
|
--muted-foreground: #6c6f85; /* subtext0 */
|
||||||
|
--accent: #1e66f5; /* blue */
|
||||||
|
--accent-foreground: #eff1f5;
|
||||||
|
--destructive: #d20f39; /* red */
|
||||||
|
--border: #bcc0cc; /* surface1 */
|
||||||
|
--input: #bcc0cc;
|
||||||
|
--ring: #8839ef;
|
||||||
|
|
||||||
|
--success: #40a02b; /* green */
|
||||||
|
--success-foreground: #eff1f5;
|
||||||
|
--warning: #df8e1d; /* yellow */
|
||||||
|
--warning-foreground: #4c4f69;
|
||||||
|
--bitcoin: #fe640b; /* peach */
|
||||||
|
--bitcoin-foreground: #eff1f5;
|
||||||
|
|
||||||
|
--qr: #eff1f5;
|
||||||
|
--qr-foreground: #4c4f69;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='catppuccin'].dark {
|
||||||
|
/* Catppuccin Mocha (dark) */
|
||||||
|
--background: #1e1e2e; /* base */
|
||||||
|
--foreground: #cdd6f4; /* text */
|
||||||
|
--card: #181825; /* mantle */
|
||||||
|
--card-foreground: #cdd6f4;
|
||||||
|
--popover: #181825;
|
||||||
|
--popover-foreground: #cdd6f4;
|
||||||
|
--primary: #cba6f7; /* mauve */
|
||||||
|
--primary-foreground: #1e1e2e;
|
||||||
|
--secondary: #313244; /* surface0 */
|
||||||
|
--secondary-foreground: #cdd6f4;
|
||||||
|
--muted: #313244;
|
||||||
|
--muted-foreground: #a6adc8; /* subtext0 */
|
||||||
|
--accent: #89b4fa; /* blue */
|
||||||
|
--accent-foreground: #1e1e2e;
|
||||||
|
--destructive: #f38ba8; /* red */
|
||||||
|
--border: #45475a; /* surface1 */
|
||||||
|
--input: #45475a;
|
||||||
|
--ring: #cba6f7;
|
||||||
|
|
||||||
|
--success: #a6e3a1; /* green */
|
||||||
|
--success-foreground: #1e1e2e;
|
||||||
|
--warning: #f9e2af; /* yellow */
|
||||||
|
--warning-foreground: #1e1e2e;
|
||||||
|
--bitcoin: #fab387; /* peach */
|
||||||
|
--bitcoin-foreground: #1e1e2e;
|
||||||
|
|
||||||
|
--qr: #eff1f5;
|
||||||
|
--qr-foreground: #1e1e2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cyberpunk Palette
|
||||||
|
* Neon accents on deep dark backgrounds
|
||||||
|
*/
|
||||||
|
|
||||||
|
[data-theme='cyberpunk'] {
|
||||||
|
--radius: 0.375rem;
|
||||||
|
|
||||||
|
/* Cyberpunk Light (corporate dystopia) */
|
||||||
|
--background: #f0f0f8;
|
||||||
|
--foreground: #1a1a2e;
|
||||||
|
--card: #e4e4f0;
|
||||||
|
--card-foreground: #1a1a2e;
|
||||||
|
--popover: #e4e4f0;
|
||||||
|
--popover-foreground: #1a1a2e;
|
||||||
|
--primary: #d6006e; /* magenta */
|
||||||
|
--primary-foreground: #f0f0f8;
|
||||||
|
--secondary: #d0d0e0;
|
||||||
|
--secondary-foreground: #1a1a2e;
|
||||||
|
--muted: #d0d0e0;
|
||||||
|
--muted-foreground: #6b6b8a;
|
||||||
|
--accent: #0099b3; /* teal */
|
||||||
|
--accent-foreground: #f0f0f8;
|
||||||
|
--destructive: #cc0033;
|
||||||
|
--border: #b8b8d0;
|
||||||
|
--input: #b8b8d0;
|
||||||
|
--ring: #d6006e;
|
||||||
|
|
||||||
|
--success: #00b359;
|
||||||
|
--success-foreground: #f0f0f8;
|
||||||
|
--warning: #cc9900;
|
||||||
|
--warning-foreground: #1a1a2e;
|
||||||
|
--bitcoin: #f7931a;
|
||||||
|
--bitcoin-foreground: #f0f0f8;
|
||||||
|
|
||||||
|
--qr: #f0f0f8;
|
||||||
|
--qr-foreground: #1a1a2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='cyberpunk'].dark {
|
||||||
|
/* Cyberpunk Dark (neon noir) */
|
||||||
|
--background: #0a0a0f;
|
||||||
|
--foreground: #e0e0ff;
|
||||||
|
--card: #12121f;
|
||||||
|
--card-foreground: #e0e0ff;
|
||||||
|
--popover: #12121f;
|
||||||
|
--popover-foreground: #e0e0ff;
|
||||||
|
--primary: #ff2a6d; /* neon magenta */
|
||||||
|
--primary-foreground: #0a0a0f;
|
||||||
|
--secondary: #1a1a2e;
|
||||||
|
--secondary-foreground: #e0e0ff;
|
||||||
|
--muted: #1a1a2e;
|
||||||
|
--muted-foreground: #6b6b8a;
|
||||||
|
--accent: #05d9e8; /* neon cyan */
|
||||||
|
--accent-foreground: #0a0a0f;
|
||||||
|
--destructive: #ff0040;
|
||||||
|
--border: #2a2a4a;
|
||||||
|
--input: #2a2a4a;
|
||||||
|
--ring: #ff2a6d;
|
||||||
|
|
||||||
|
--success: #01ff70; /* neon green */
|
||||||
|
--success-foreground: #0a0a0f;
|
||||||
|
--warning: #ffdd00; /* neon yellow */
|
||||||
|
--warning-foreground: #0a0a0f;
|
||||||
|
--bitcoin: #f7931a;
|
||||||
|
--bitcoin-foreground: #0a0a0f;
|
||||||
|
|
||||||
|
--qr: #e0e0ff;
|
||||||
|
--qr-foreground: #0a0a0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dracula Palette
|
||||||
|
* https://draculatheme.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
[data-theme='dracula'] {
|
||||||
|
--radius: 0.625rem;
|
||||||
|
|
||||||
|
/* Dracula Light (adapted) */
|
||||||
|
--background: #f8f8f2;
|
||||||
|
--foreground: #282a36;
|
||||||
|
--card: #e8e8e2;
|
||||||
|
--card-foreground: #282a36;
|
||||||
|
--popover: #e8e8e2;
|
||||||
|
--popover-foreground: #282a36;
|
||||||
|
--primary: #7c3aed; /* purple */
|
||||||
|
--primary-foreground: #f8f8f2;
|
||||||
|
--secondary: #d8d8d2;
|
||||||
|
--secondary-foreground: #282a36;
|
||||||
|
--muted: #d8d8d2;
|
||||||
|
--muted-foreground: #6272a4; /* comment */
|
||||||
|
--accent: #0891b2; /* cyan */
|
||||||
|
--accent-foreground: #f8f8f2;
|
||||||
|
--destructive: #dc2626;
|
||||||
|
--border: #c8c8c2;
|
||||||
|
--input: #c8c8c2;
|
||||||
|
--ring: #7c3aed;
|
||||||
|
|
||||||
|
--success: #16a34a;
|
||||||
|
--success-foreground: #f8f8f2;
|
||||||
|
--warning: #ca8a04;
|
||||||
|
--warning-foreground: #282a36;
|
||||||
|
--bitcoin: #ea580c;
|
||||||
|
--bitcoin-foreground: #f8f8f2;
|
||||||
|
|
||||||
|
--qr: #f8f8f2;
|
||||||
|
--qr-foreground: #282a36;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='dracula'].dark {
|
||||||
|
/* Dracula Dark */
|
||||||
|
--background: #282a36; /* background */
|
||||||
|
--foreground: #f8f8f2; /* foreground */
|
||||||
|
--card: #44475a; /* current line */
|
||||||
|
--card-foreground: #f8f8f2;
|
||||||
|
--popover: #44475a;
|
||||||
|
--popover-foreground: #f8f8f2;
|
||||||
|
--primary: #bd93f9; /* purple */
|
||||||
|
--primary-foreground: #282a36;
|
||||||
|
--secondary: #44475a;
|
||||||
|
--secondary-foreground: #f8f8f2;
|
||||||
|
--muted: #44475a;
|
||||||
|
--muted-foreground: #6272a4; /* comment */
|
||||||
|
--accent: #8be9fd; /* cyan */
|
||||||
|
--accent-foreground: #282a36;
|
||||||
|
--destructive: #ff5555; /* red */
|
||||||
|
--border: #6272a4;
|
||||||
|
--input: #6272a4;
|
||||||
|
--ring: #bd93f9;
|
||||||
|
|
||||||
|
--success: #50fa7b; /* green */
|
||||||
|
--success-foreground: #282a36;
|
||||||
|
--warning: #f1fa8c; /* yellow */
|
||||||
|
--warning-foreground: #282a36;
|
||||||
|
--bitcoin: #ffb86c; /* orange */
|
||||||
|
--bitcoin-foreground: #282a36;
|
||||||
|
|
||||||
|
--qr: #f8f8f2;
|
||||||
|
--qr-foreground: #282a36;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Nord Palette
|
||||||
|
* https://www.nordtheme.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
[data-theme='nord'] {
|
||||||
|
--radius: 0.5rem;
|
||||||
|
|
||||||
|
/* Nord Light (Snow Storm) */
|
||||||
|
--background: #eceff4; /* snow storm 3 */
|
||||||
|
--foreground: #2e3440; /* polar night 1 */
|
||||||
|
--card: #e5e9f0; /* snow storm 2 */
|
||||||
|
--card-foreground: #2e3440;
|
||||||
|
--popover: #e5e9f0;
|
||||||
|
--popover-foreground: #2e3440;
|
||||||
|
--primary: #5e81ac; /* frost 4 */
|
||||||
|
--primary-foreground: #eceff4;
|
||||||
|
--secondary: #d8dee9; /* snow storm 1 */
|
||||||
|
--secondary-foreground: #2e3440;
|
||||||
|
--muted: #d8dee9;
|
||||||
|
--muted-foreground: #4c566a; /* polar night 4 */
|
||||||
|
--accent: #88c0d0; /* frost 2 */
|
||||||
|
--accent-foreground: #2e3440;
|
||||||
|
--destructive: #bf616a; /* aurora red */
|
||||||
|
--border: #d8dee9;
|
||||||
|
--input: #d8dee9;
|
||||||
|
--ring: #5e81ac;
|
||||||
|
|
||||||
|
--success: #a3be8c; /* aurora green */
|
||||||
|
--success-foreground: #2e3440;
|
||||||
|
--warning: #ebcb8b; /* aurora yellow */
|
||||||
|
--warning-foreground: #2e3440;
|
||||||
|
--bitcoin: #d08770; /* aurora orange */
|
||||||
|
--bitcoin-foreground: #eceff4;
|
||||||
|
|
||||||
|
--qr: #eceff4;
|
||||||
|
--qr-foreground: #2e3440;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='nord'].dark {
|
||||||
|
/* Nord Dark (Polar Night) */
|
||||||
|
--background: #2e3440; /* polar night 1 */
|
||||||
|
--foreground: #eceff4; /* snow storm 3 */
|
||||||
|
--card: #3b4252; /* polar night 2 */
|
||||||
|
--card-foreground: #eceff4;
|
||||||
|
--popover: #3b4252;
|
||||||
|
--popover-foreground: #eceff4;
|
||||||
|
--primary: #88c0d0; /* frost 2 */
|
||||||
|
--primary-foreground: #2e3440;
|
||||||
|
--secondary: #434c5e; /* polar night 3 */
|
||||||
|
--secondary-foreground: #eceff4;
|
||||||
|
--muted: #434c5e;
|
||||||
|
--muted-foreground: #d8dee9; /* snow storm 1 */
|
||||||
|
--accent: #81a1c1; /* frost 3 */
|
||||||
|
--accent-foreground: #2e3440;
|
||||||
|
--destructive: #bf616a; /* aurora red */
|
||||||
|
--border: #434c5e;
|
||||||
|
--input: #434c5e;
|
||||||
|
--ring: #88c0d0;
|
||||||
|
|
||||||
|
--success: #a3be8c; /* aurora green */
|
||||||
|
--success-foreground: #2e3440;
|
||||||
|
--warning: #ebcb8b; /* aurora yellow */
|
||||||
|
--warning-foreground: #2e3440;
|
||||||
|
--bitcoin: #d08770; /* aurora orange */
|
||||||
|
--bitcoin-foreground: #2e3440;
|
||||||
|
|
||||||
|
--qr: #eceff4;
|
||||||
|
--qr-foreground: #2e3440;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tokyo Night Palette
|
||||||
|
* https://github.com/enkia/tokyo-night-vscode-theme
|
||||||
|
*/
|
||||||
|
|
||||||
|
[data-theme='tokyo-night'] {
|
||||||
|
--radius: 0.5rem;
|
||||||
|
|
||||||
|
/* Tokyo Night Day (light) */
|
||||||
|
--background: #e1e2e7; /* bg */
|
||||||
|
--foreground: #3760bf; /* fg */
|
||||||
|
--card: #d5d6db;
|
||||||
|
--card-foreground: #3760bf;
|
||||||
|
--popover: #d5d6db;
|
||||||
|
--popover-foreground: #3760bf;
|
||||||
|
--primary: #9854f1; /* magenta */
|
||||||
|
--primary-foreground: #e1e2e7;
|
||||||
|
--secondary: #c4c5ca;
|
||||||
|
--secondary-foreground: #3760bf;
|
||||||
|
--muted: #c4c5ca;
|
||||||
|
--muted-foreground: #848cb5; /* comment */
|
||||||
|
--accent: #2e7de9; /* blue */
|
||||||
|
--accent-foreground: #e1e2e7;
|
||||||
|
--destructive: #f52a65; /* red */
|
||||||
|
--border: #b4b5ba;
|
||||||
|
--input: #b4b5ba;
|
||||||
|
--ring: #9854f1;
|
||||||
|
|
||||||
|
--success: #587539; /* green */
|
||||||
|
--success-foreground: #e1e2e7;
|
||||||
|
--warning: #8c6c3e; /* yellow */
|
||||||
|
--warning-foreground: #e1e2e7;
|
||||||
|
--bitcoin: #b15c00; /* orange */
|
||||||
|
--bitcoin-foreground: #e1e2e7;
|
||||||
|
|
||||||
|
--qr: #e1e2e7;
|
||||||
|
--qr-foreground: #3760bf;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='tokyo-night'].dark {
|
||||||
|
/* Tokyo Night Storm (dark) */
|
||||||
|
--background: #1a1b26; /* bg */
|
||||||
|
--foreground: #c0caf5; /* fg */
|
||||||
|
--card: #24283b; /* bg_highlight */
|
||||||
|
--card-foreground: #c0caf5;
|
||||||
|
--popover: #24283b;
|
||||||
|
--popover-foreground: #c0caf5;
|
||||||
|
--primary: #bb9af7; /* magenta */
|
||||||
|
--primary-foreground: #1a1b26;
|
||||||
|
--secondary: #292e42;
|
||||||
|
--secondary-foreground: #c0caf5;
|
||||||
|
--muted: #292e42;
|
||||||
|
--muted-foreground: #565f89; /* comment */
|
||||||
|
--accent: #7aa2f7; /* blue */
|
||||||
|
--accent-foreground: #1a1b26;
|
||||||
|
--destructive: #f7768e; /* red */
|
||||||
|
--border: #3b4261;
|
||||||
|
--input: #3b4261;
|
||||||
|
--ring: #bb9af7;
|
||||||
|
|
||||||
|
--success: #9ece6a; /* green */
|
||||||
|
--success-foreground: #1a1b26;
|
||||||
|
--warning: #e0af68; /* yellow */
|
||||||
|
--warning-foreground: #1a1b26;
|
||||||
|
--bitcoin: #ff9e64; /* orange */
|
||||||
|
--bitcoin-foreground: #1a1b26;
|
||||||
|
|
||||||
|
--qr: #c0caf5;
|
||||||
|
--qr-foreground: #1a1b26;
|
||||||
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border outline-ring/50;
|
@apply border-border outline-ring/50;
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,15 @@
|
||||||
import { watch } from 'vue'
|
import { watch } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useAtmStore } from '@/stores/atm'
|
import { useAtmStore } from '@/stores/atm'
|
||||||
|
import { useTheme } from '@/composables/useTheme'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Card, CardContent } from '@/components/ui/card'
|
import { Card, CardContent } from '@/components/ui/card'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
|
import BitcoinIcon from '@/components/BitcoinIcon.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const atmStore = useAtmStore()
|
const atmStore = useAtmStore()
|
||||||
|
const { current: currentTheme, themes } = useTheme()
|
||||||
|
|
||||||
// Watch for state changes and navigate accordingly
|
// Watch for state changes and navigate accordingly
|
||||||
watch(
|
watch(
|
||||||
|
|
@ -45,7 +48,7 @@ function handleCashOut() {
|
||||||
@click="handleCashIn"
|
@click="handleCashIn"
|
||||||
>
|
>
|
||||||
<CardContent class="flex h-full flex-col items-center justify-center gap-2 p-6">
|
<CardContent class="flex h-full flex-col items-center justify-center gap-2 p-6">
|
||||||
<span class="text-5xl">💵</span>
|
<BitcoinIcon :size="96" />
|
||||||
<span class="text-xl font-bold text-success">Buy Bitcoin</span>
|
<span class="text-xl font-bold text-success">Buy Bitcoin</span>
|
||||||
<span class="text-sm text-foreground/70">Insert cash, receive sats</span>
|
<span class="text-sm text-foreground/70">Insert cash, receive sats</span>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
@ -71,6 +74,21 @@ function handleCashOut() {
|
||||||
<Badge variant="outline" class="border-bitcoin text-bitcoin">Just Bitcoin</Badge>
|
<Badge variant="outline" class="border-bitcoin text-bitcoin">Just Bitcoin</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Theme switcher -->
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-xs text-muted-foreground">Theme:</span>
|
||||||
|
<Button
|
||||||
|
v-for="theme in themes"
|
||||||
|
:key="theme.id"
|
||||||
|
:variant="currentTheme === theme.id ? 'default' : 'outline'"
|
||||||
|
size="sm"
|
||||||
|
class="h-7 text-xs"
|
||||||
|
@click="currentTheme = theme.id"
|
||||||
|
>
|
||||||
|
{{ theme.label }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Debug toggle -->
|
<!-- Debug toggle -->
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue