feat: redesign landing page with shadcn-vue components
Clean component architecture with proper spacing and visual hierarchy: - HeroSection: logo, gradient heading, subtitle with generous whitespace - InfoCard: single unified Card with divide-y sections - RatesSection: side-by-side cash-in/out with vertical separator - AtmStatusList: live Nostr status with Badge and Skeleton loading - ContactLinks: lucide Send/Mail icons with ghost Button links - Catppuccin theme with bitcoin as a proper semantic color token Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f440379a89
commit
d899aff199
9 changed files with 173 additions and 251 deletions
44
src/App.vue
44
src/App.vue
|
|
@ -1,47 +1,21 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import HeroSection from './components/HeroSection.vue'
|
import HeroSection from '@/components/HeroSection.vue'
|
||||||
import BtcmatCard from './components/BtcmatCard.vue'
|
import InfoCard from '@/components/InfoCard.vue'
|
||||||
import AtmStatus from './components/AtmStatus.vue'
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="relative min-h-dvh flex items-center justify-center p-8 max-md:p-3 overflow-hidden">
|
<div class="relative min-h-dvh flex flex-col items-center justify-center overflow-hidden">
|
||||||
<!-- Background image -->
|
<!-- Full-bleed background -->
|
||||||
<img
|
<img
|
||||||
src="/atio_bg.webp"
|
src="/atio_bg.webp"
|
||||||
alt=""
|
alt=""
|
||||||
class="fixed inset-0 w-full h-full object-cover opacity-15 pointer-events-none"
|
class="fixed inset-0 w-full h-full object-cover opacity-10 pointer-events-none select-none"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Content -->
|
<!-- Page content — vertically centered, generous whitespace -->
|
||||||
<div class="relative z-10 text-center animate-fade-in-up">
|
<main class="relative z-10 w-full max-w-xl px-6 py-16 max-md:px-4 max-md:py-10 space-y-12 max-md:space-y-8">
|
||||||
<HeroSection />
|
<HeroSection />
|
||||||
|
<InfoCard />
|
||||||
<div class="mt-12 max-md:mt-6 space-y-6 max-md:space-y-4 max-w-md mx-auto">
|
</main>
|
||||||
<BtcmatCard />
|
|
||||||
<AtmStatus />
|
|
||||||
|
|
||||||
<!-- Contact -->
|
|
||||||
<div class="rounded-xl bg-card/50 backdrop-blur-md border border-border/50 p-6 max-md:p-4">
|
|
||||||
<p class="text-muted-foreground mb-3">Contact:</p>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<a
|
|
||||||
href="https://t.me/atitlanio"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
class="flex items-center justify-center gap-2 text-bitcoin rounded-lg bg-muted/50 px-3 py-2 transition-shadow hover:shadow-lg hover:shadow-bitcoin/10"
|
|
||||||
>
|
|
||||||
<span>✈</span> Telegram @atitlanio
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="mailto:atitlanio@protonmail.com"
|
|
||||||
class="flex items-center justify-center gap-2 text-bitcoin rounded-lg bg-muted/50 px-3 py-2 transition-shadow hover:shadow-lg hover:shadow-bitcoin/10"
|
|
||||||
>
|
|
||||||
<span>✉</span> atitlanio@protonmail.com
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,165 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
|
||||||
|
|
||||||
const RELAY = 'wss://strfry.atitlan.io'
|
|
||||||
const OFFLINE_THRESHOLD = 10 * 60 // 10 minutes
|
|
||||||
|
|
||||||
interface Atm {
|
|
||||||
pubkey: string
|
|
||||||
name: string
|
|
||||||
location: string
|
|
||||||
online: boolean
|
|
||||||
maintenance: boolean
|
|
||||||
cashIn: boolean
|
|
||||||
cashOut: boolean
|
|
||||||
cashLevel: 'none' | 'low' | 'good' | 'full'
|
|
||||||
lastSeen: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const atms = ref<Record<string, Atm>>({
|
|
||||||
douro: {
|
|
||||||
pubkey: 'b22bd8a7759fa32d57f0061935a5af38d8598d11bbb700c6dec0d352bd0b6ade',
|
|
||||||
name: 'Bitcoinmat',
|
|
||||||
location: 'Trece Cielos',
|
|
||||||
online: false,
|
|
||||||
maintenance: false,
|
|
||||||
cashIn: false,
|
|
||||||
cashOut: false,
|
|
||||||
cashLevel: 'none',
|
|
||||||
lastSeen: 0,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
let ws: WebSocket | null = null
|
|
||||||
let refreshInterval: ReturnType<typeof setInterval> | null = null
|
|
||||||
const now = ref(Math.floor(Date.now() / 1000))
|
|
||||||
|
|
||||||
function formatAgo(seconds: number): string {
|
|
||||||
if (seconds < 60) return 'just now'
|
|
||||||
if (seconds < 3600) return Math.floor(seconds / 60) + 'm ago'
|
|
||||||
if (seconds < 86400) return Math.floor(seconds / 3600) + 'h ago'
|
|
||||||
return Math.floor(seconds / 86400) + 'd ago'
|
|
||||||
}
|
|
||||||
|
|
||||||
function isOnline(atm: Atm): boolean {
|
|
||||||
return atm.lastSeen > 0 && (now.value - atm.lastSeen) < OFFLINE_THRESHOLD
|
|
||||||
}
|
|
||||||
|
|
||||||
function statusClass(atm: Atm): string {
|
|
||||||
if (atm.maintenance) return 'bg-chart-3 shadow-[0_0_6px_oklch(var(--chart-3)/0.4)]'
|
|
||||||
if (isOnline(atm)) return 'bg-chart-4 shadow-[0_0_6px_oklch(var(--chart-4)/0.4)]'
|
|
||||||
return 'bg-destructive shadow-[0_0_6px_oklch(var(--destructive)/0.4)]'
|
|
||||||
}
|
|
||||||
|
|
||||||
function statusText(atm: Atm): string {
|
|
||||||
if (atm.maintenance) return 'under service'
|
|
||||||
if (isOnline(atm)) return formatAgo(now.value - atm.lastSeen)
|
|
||||||
return 'offline'
|
|
||||||
}
|
|
||||||
|
|
||||||
const levelLabels: Record<string, string> = {
|
|
||||||
none: 'No cash',
|
|
||||||
low: 'Low cash',
|
|
||||||
good: 'Cash available',
|
|
||||||
full: 'Fully stocked',
|
|
||||||
}
|
|
||||||
|
|
||||||
function levelClass(level: string, online: boolean): string {
|
|
||||||
if (!online) return 'bg-muted/50 text-muted-foreground'
|
|
||||||
if (level === 'good' || level === 'full') return 'bg-chart-4/15 text-chart-4'
|
|
||||||
if (level === 'low') return 'bg-chart-3/15 text-chart-3'
|
|
||||||
return 'bg-muted/50 text-muted-foreground'
|
|
||||||
}
|
|
||||||
|
|
||||||
function connectRelay() {
|
|
||||||
ws = new WebSocket(RELAY)
|
|
||||||
|
|
||||||
ws.onopen = () => {
|
|
||||||
const authors = Object.values(atms.value).map((a) => a.pubkey)
|
|
||||||
ws!.send(JSON.stringify(['REQ', 'atm-status', { kinds: [30078], authors, '#d': ['atm-availability'] }]))
|
|
||||||
}
|
|
||||||
|
|
||||||
ws.onmessage = (msg) => {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(msg.data)
|
|
||||||
if (data[0] !== 'EVENT') return
|
|
||||||
|
|
||||||
const event = data[2]
|
|
||||||
const content = JSON.parse(event.content)
|
|
||||||
|
|
||||||
for (const [id, atm] of Object.entries(atms.value)) {
|
|
||||||
if (atm.pubkey === event.pubkey) {
|
|
||||||
atms.value[id] = {
|
|
||||||
...atm,
|
|
||||||
cashIn: content.cash_in || false,
|
|
||||||
cashOut: content.cash_out || false,
|
|
||||||
cashLevel: content.cash_level || 'none',
|
|
||||||
maintenance: content.maintenance || false,
|
|
||||||
online: true,
|
|
||||||
lastSeen: event.created_at,
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// ignore parse errors
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ws.onclose = () => {
|
|
||||||
setTimeout(connectRelay, 5000)
|
|
||||||
}
|
|
||||||
|
|
||||||
ws.onerror = () => {
|
|
||||||
ws?.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
connectRelay()
|
|
||||||
refreshInterval = setInterval(() => {
|
|
||||||
now.value = Math.floor(Date.now() / 1000)
|
|
||||||
}, 30000)
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
ws?.close()
|
|
||||||
ws = null
|
|
||||||
if (refreshInterval) clearInterval(refreshInterval)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="rounded-xl bg-foreground/5 backdrop-blur-md border border-foreground/10 p-5 max-md:p-4">
|
|
||||||
<h3 class="mb-3 text-sm uppercase tracking-wider text-muted-foreground">
|
|
||||||
ATM Status
|
|
||||||
</h3>
|
|
||||||
<div v-for="(atm, id) in atms" :key="id" class="flex items-center gap-3 py-2">
|
|
||||||
<div class="w-2.5 h-2.5 rounded-full shrink-0" :class="statusClass(atm)" />
|
|
||||||
<div class="flex-1 text-left">
|
|
||||||
<span class="font-semibold text-sm text-foreground/90">{{ atm.name }}</span>
|
|
||||||
<span class="block text-xs text-foreground/30">{{ atm.location }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-1.5 text-[0.7rem]">
|
|
||||||
<template v-if="atm.maintenance">
|
|
||||||
<span class="px-1.5 py-0.5 rounded bg-chart-3/15 text-chart-3">Maintenance</span>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<span
|
|
||||||
class="px-1.5 py-0.5 rounded"
|
|
||||||
:class="atm.cashIn ? 'bg-chart-4/15 text-chart-4' : 'bg-muted/50 text-muted-foreground'"
|
|
||||||
>
|
|
||||||
Buy ₿
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="px-1.5 py-0.5 rounded"
|
|
||||||
:class="levelClass(atm.cashLevel, isOnline(atm))"
|
|
||||||
>
|
|
||||||
{{ isOnline(atm) ? (levelLabels[atm.cashLevel] || 'Sell ₿') : 'Sell ₿' }}
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<span class="text-xs text-foreground/40 ml-auto">{{ statusText(atm) }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
84
src/components/AtmStatusList.vue
Normal file
84
src/components/AtmStatusList.vue
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useNostrAtmStatus } from '@/composables/useNostrAtmStatus'
|
||||||
|
import { Badge } from '@/components/ui/badge'
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton'
|
||||||
|
|
||||||
|
const { atms, loading, isOnline, statusText } = useNostrAtmStatus()
|
||||||
|
|
||||||
|
const cashLabels: Record<string, string> = {
|
||||||
|
none: 'No cash',
|
||||||
|
low: 'Low cash',
|
||||||
|
good: 'Cash OK',
|
||||||
|
full: 'Stocked',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="space-y-4">
|
||||||
|
<h3 class="text-xs font-medium uppercase tracking-widest text-muted-foreground text-center">
|
||||||
|
ATM Status
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<!-- Loading skeleton -->
|
||||||
|
<div v-if="loading" class="space-y-3">
|
||||||
|
<div v-for="i in atms.size" :key="i" class="flex items-center gap-3">
|
||||||
|
<Skeleton class="size-2 rounded-full" />
|
||||||
|
<Skeleton class="h-4 w-28" />
|
||||||
|
<Skeleton class="ml-auto h-5 w-20" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ATM rows -->
|
||||||
|
<div v-else class="space-y-3">
|
||||||
|
<div
|
||||||
|
v-for="[id, atm] in atms"
|
||||||
|
:key="id"
|
||||||
|
class="flex items-center gap-3"
|
||||||
|
>
|
||||||
|
<!-- Status dot -->
|
||||||
|
<span
|
||||||
|
class="size-2 rounded-full shrink-0"
|
||||||
|
:class="{
|
||||||
|
'bg-chart-3': atm.maintenance,
|
||||||
|
'bg-chart-4': !atm.maintenance && isOnline(atm),
|
||||||
|
'bg-destructive': !atm.maintenance && !isOnline(atm),
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Machine info -->
|
||||||
|
<div class="min-w-0">
|
||||||
|
<span class="text-sm font-medium text-card-foreground">{{ atm.config.name }}</span>
|
||||||
|
<span class="text-muted-foreground text-xs ml-1.5">{{ atm.config.location }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Service badges -->
|
||||||
|
<div class="ml-auto flex items-center gap-1.5">
|
||||||
|
<Badge v-if="atm.maintenance" variant="secondary" class="text-chart-3 text-xs">
|
||||||
|
Maintenance
|
||||||
|
</Badge>
|
||||||
|
<template v-else>
|
||||||
|
<Badge
|
||||||
|
variant="secondary"
|
||||||
|
class="text-xs"
|
||||||
|
:class="atm.cashIn ? 'text-chart-4' : 'text-muted-foreground'"
|
||||||
|
>
|
||||||
|
Buy ₿
|
||||||
|
</Badge>
|
||||||
|
<Badge
|
||||||
|
variant="secondary"
|
||||||
|
class="text-xs"
|
||||||
|
:class="{
|
||||||
|
'text-chart-4': isOnline(atm) && (atm.cashLevel === 'good' || atm.cashLevel === 'full'),
|
||||||
|
'text-chart-3': isOnline(atm) && atm.cashLevel === 'low',
|
||||||
|
'text-muted-foreground': !isOnline(atm) || atm.cashLevel === 'none',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ isOnline(atm) ? (cashLabels[atm.cashLevel] || 'Sell ₿') : 'Sell ₿' }}
|
||||||
|
</Badge>
|
||||||
|
</template>
|
||||||
|
<span class="text-xs text-muted-foreground ml-1">{{ statusText(atm) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
class="rounded-xl bg-card/50 backdrop-blur-md border border-border/50 p-8 max-md:p-4 transition-all duration-300 hover:-translate-y-1 hover:shadow-lg hover:shadow-primary/10 hover:border-primary/30"
|
|
||||||
>
|
|
||||||
<h2 class="text-3xl max-md:text-xl font-normal mb-6 max-md:mb-3 text-bitcoin">
|
|
||||||
Bitcoinmat
|
|
||||||
</h2>
|
|
||||||
<div class="space-y-2 text-lg max-md:text-sm">
|
|
||||||
<p class="text-muted-foreground">
|
|
||||||
Cash-in rate: <strong class="text-bitcoin">3%</strong>
|
|
||||||
</p>
|
|
||||||
<p class="text-muted-foreground">
|
|
||||||
Cash-out rate: <strong class="text-bitcoin">8.75%</strong>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
26
src/components/ContactLinks.vue
Normal file
26
src/components/ContactLinks.vue
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Send, Mail } from 'lucide-vue-next'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="space-y-3">
|
||||||
|
<h3 class="text-xs font-medium uppercase tracking-widest text-muted-foreground text-center">
|
||||||
|
Contact
|
||||||
|
</h3>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<Button variant="ghost" class="w-full justify-center gap-2 text-bitcoin hover:text-bitcoin hover:bg-secondary" as-child>
|
||||||
|
<a href="https://t.me/atitlanio" target="_blank" rel="noopener">
|
||||||
|
<Send class="size-4" />
|
||||||
|
<span>Telegram @atitlanio</span>
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
<Button variant="ghost" class="w-full justify-center gap-2 text-bitcoin hover:text-bitcoin hover:bg-secondary" as-child>
|
||||||
|
<a href="mailto:atitlanio@protonmail.com">
|
||||||
|
<Mail class="size-4" />
|
||||||
|
<span>atitlanio@protonmail.com</span>
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<header class="text-center space-y-5 max-md:space-y-3 animate-fade-in">
|
||||||
<img
|
<img
|
||||||
src="/logo.png"
|
src="/logo.png"
|
||||||
alt="Atitlan.io"
|
alt="Atitlan.io"
|
||||||
class="w-36 max-md:w-20 mx-auto mb-6 max-md:mb-2 drop-shadow-[0_4px_20px_oklch(0.65_0.20_180/0.3)] animate-float"
|
class="w-32 max-md:w-20 mx-auto drop-shadow-lg animate-float"
|
||||||
/>
|
/>
|
||||||
<h1
|
<h1 class="text-7xl max-md:text-4xl font-extralight tracking-tight leading-none pb-1 bg-gradient-to-r from-foreground to-bitcoin bg-clip-text text-transparent">
|
||||||
class="text-6xl max-md:text-3xl font-light mb-4 max-md:mb-2 bg-gradient-to-r from-foreground to-bitcoin bg-clip-text text-transparent animate-glow"
|
|
||||||
>
|
|
||||||
Coming Soon
|
Coming Soon
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-2xl max-md:text-base font-extralight tracking-widest text-primary">
|
<p class="text-xl max-md:text-base font-light tracking-[0.2em] text-primary">
|
||||||
Atitlan.io
|
Atitlan.io
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</header>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
16
src/components/InfoCard.vue
Normal file
16
src/components/InfoCard.vue
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Card, CardContent } from '@/components/ui/card'
|
||||||
|
import RatesSection from './RatesSection.vue'
|
||||||
|
import AtmStatusList from './AtmStatusList.vue'
|
||||||
|
import ContactLinks from './ContactLinks.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Card class="animate-fade-in [animation-delay:200ms] backdrop-blur-lg bg-card/40 border-border/40 shadow-xl">
|
||||||
|
<CardContent class="p-0 divide-y divide-border/40">
|
||||||
|
<RatesSection class="px-8 py-7 max-md:px-5 max-md:py-5" />
|
||||||
|
<AtmStatusList class="px-8 py-7 max-md:px-5 max-md:py-5" />
|
||||||
|
<ContactLinks class="px-8 py-7 max-md:px-5 max-md:py-5" />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
22
src/components/RatesSection.vue
Normal file
22
src/components/RatesSection.vue
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<template>
|
||||||
|
<section class="text-center space-y-4">
|
||||||
|
<h2 class="text-2xl max-md:text-lg font-medium text-bitcoin">
|
||||||
|
Bitcoinmat
|
||||||
|
</h2>
|
||||||
|
<div class="flex justify-center gap-8 max-md:gap-4 text-sm max-md:text-xs">
|
||||||
|
<div>
|
||||||
|
<p class="text-muted-foreground">Cash-in</p>
|
||||||
|
<p class="text-lg max-md:text-base font-semibold text-bitcoin">3%</p>
|
||||||
|
</div>
|
||||||
|
<Separator orientation="vertical" class="h-auto" />
|
||||||
|
<div>
|
||||||
|
<p class="text-muted-foreground">Cash-out</p>
|
||||||
|
<p class="text-lg max-md:text-base font-semibold text-bitcoin">8.75%</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Separator } from '@/components/ui/separator'
|
||||||
|
</script>
|
||||||
|
|
@ -33,41 +33,21 @@
|
||||||
--color-chart-3: oklch(var(--chart-3));
|
--color-chart-3: oklch(var(--chart-3));
|
||||||
--color-chart-4: oklch(var(--chart-4));
|
--color-chart-4: oklch(var(--chart-4));
|
||||||
--color-chart-5: oklch(var(--chart-5));
|
--color-chart-5: oklch(var(--chart-5));
|
||||||
|
--color-bitcoin: oklch(var(--bitcoin));
|
||||||
/* Bitcoin orange for branding */
|
--color-bitcoin-foreground: oklch(var(--bitcoin-foreground));
|
||||||
--color-bitcoin: oklch(0.75 0.18 55);
|
|
||||||
--color-bitcoin-foreground: oklch(1.0 0 0);
|
|
||||||
|
|
||||||
--animate-accordion-down: accordion-down 0.2s ease-out;
|
|
||||||
--animate-accordion-up: accordion-up 0.2s ease-out;
|
|
||||||
|
|
||||||
@keyframes accordion-down {
|
|
||||||
from { height: 0; }
|
|
||||||
to { height: var(--reka-accordion-content-height); }
|
|
||||||
}
|
|
||||||
@keyframes accordion-up {
|
|
||||||
from { height: var(--reka-accordion-content-height); }
|
|
||||||
to { height: 0; }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes float {
|
@keyframes float {
|
||||||
0%, 100% { transform: translateY(0); }
|
0%, 100% { transform: translateY(0); }
|
||||||
50% { transform: translateY(-10px); }
|
50% { transform: translateY(-8px); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fade-in-up {
|
@keyframes fade-in {
|
||||||
from { opacity: 0; transform: translateY(20px); }
|
from { opacity: 0; transform: translateY(16px); }
|
||||||
to { opacity: 1; transform: translateY(0); }
|
to { opacity: 1; transform: translateY(0); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes glow {
|
|
||||||
0% { text-shadow: 0 0 20px oklch(0.75 0.18 55 / 0.3); }
|
|
||||||
100% { text-shadow: 0 0 30px oklch(0.75 0.18 55 / 0.6); }
|
|
||||||
}
|
|
||||||
|
|
||||||
--animate-float: float 4s ease-in-out infinite;
|
--animate-float: float 4s ease-in-out infinite;
|
||||||
--animate-fade-in-up: fade-in-up 1s ease-out;
|
--animate-fade-in: fade-in 0.8s ease-out both;
|
||||||
--animate-glow: glow 2s ease-in-out infinite alternate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
|
@ -97,7 +77,9 @@
|
||||||
--chart-3: 0.85 0.20 90;
|
--chart-3: 0.85 0.20 90;
|
||||||
--chart-4: 0.75 0.20 150;
|
--chart-4: 0.75 0.20 150;
|
||||||
--chart-5: 0.65 0.20 180;
|
--chart-5: 0.65 0.20 180;
|
||||||
--radius: 0.5rem;
|
--bitcoin: 0.75 0.18 55;
|
||||||
|
--bitcoin-foreground: 1.0 0 0;
|
||||||
|
--radius: 0.625rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
|
|
@ -126,6 +108,8 @@
|
||||||
--chart-3: 0.88 0.18 95;
|
--chart-3: 0.88 0.18 95;
|
||||||
--chart-4: 0.80 0.18 155;
|
--chart-4: 0.80 0.18 155;
|
||||||
--chart-5: 0.70 0.18 190;
|
--chart-5: 0.70 0.18 190;
|
||||||
|
--bitcoin: 0.75 0.18 55;
|
||||||
|
--bitcoin-foreground: 1.0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
*,
|
*,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue