Readying for design
This commit is contained in:
parent
d73256ca2b
commit
fadf473dde
50 changed files with 1478 additions and 1259 deletions
|
|
@ -7,11 +7,14 @@
|
|||
]"
|
||||
>
|
||||
<!-- Logo/Brand at top (desktop only) -->
|
||||
<div v-if="!isMobile" class="p-8 border-b border-guild-700 bg-primary-500">
|
||||
<!-- Logo/Brand: designer will replace text with logo asset -->
|
||||
<div v-if="!isMobile" class="p-8 border-b border-guild-700 bg-guild-900">
|
||||
<NuxtLink to="/" class="flex flex-col items-center gap-3 group">
|
||||
<span class="text-xl font-bold text-white warm-text tracking-wider"
|
||||
>Ghost Guild Logo</span
|
||||
>
|
||||
<slot name="logo">
|
||||
<span class="text-display-sm font-bold text-candlelight-400 warm-text tracking-wider"
|
||||
>Ghost Guild</span
|
||||
>
|
||||
</slot>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
|
|
@ -112,16 +115,16 @@
|
|||
|
||||
<div
|
||||
v-if="loginSuccess"
|
||||
class="p-3 bg-green-900/20 rounded border border-green-800"
|
||||
class="p-3 bg-candlelight-900/20 rounded border border-candlelight-800"
|
||||
>
|
||||
<p class="text-green-300 text-sm">✅ Check your email!</p>
|
||||
<p class="text-candlelight-400 text-sm">Check your email!</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="loginError"
|
||||
class="p-3 bg-red-900/20 rounded border border-red-800"
|
||||
class="p-3 bg-ember-900/20 rounded border border-ember-800"
|
||||
>
|
||||
<p class="text-red-300 text-sm">{{ loginError }}</p>
|
||||
<p class="text-ember-400 text-sm">{{ loginError }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,33 +4,33 @@
|
|||
>
|
||||
<!-- Header -->
|
||||
<div
|
||||
class="bg-gradient-to-br from-purple-600 to-purple-800 dark:from-purple-700 dark:to-purple-900 p-6"
|
||||
class="bg-gradient-to-br from-candlelight-500 to-candlelight-700 dark:from-candlelight-600 dark:to-candlelight-800 p-6"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<Icon
|
||||
name="heroicons:ticket"
|
||||
class="w-5 h-5 text-purple-200 dark:text-purple-300"
|
||||
class="w-5 h-5 text-candlelight-900 dark:text-candlelight-200"
|
||||
/>
|
||||
<span class="text-sm font-semibold text-purple-200 dark:text-purple-300">
|
||||
<span class="text-sm font-semibold text-candlelight-900 dark:text-candlelight-200">
|
||||
Series Pass
|
||||
</span>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-white mb-1">
|
||||
{{ ticket.name }}
|
||||
</h3>
|
||||
<p v-if="ticket.description" class="text-sm text-purple-200 dark:text-purple-300">
|
||||
<p v-if="ticket.description" class="text-sm text-candlelight-900 dark:text-candlelight-200">
|
||||
{{ ticket.description }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-right flex-shrink-0">
|
||||
<div class="text-3xl font-bold text-white">
|
||||
<div class="text-3xl font-bold text-white text-ui-mono">
|
||||
{{ formatPrice(ticket.price, ticket.currency) }}
|
||||
</div>
|
||||
<div
|
||||
v-if="ticket.isEarlyBird"
|
||||
class="text-xs text-purple-200 dark:text-purple-300 mt-1"
|
||||
class="text-xs text-candlelight-900 dark:text-candlelight-200 mt-1"
|
||||
>
|
||||
Early Bird Price
|
||||
</div>
|
||||
|
|
@ -47,21 +47,21 @@
|
|||
</h4>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center gap-2 text-guild-300 dark:text-guild-300">
|
||||
<Icon name="heroicons:check-circle" class="w-5 h-5 text-green-400" />
|
||||
<Icon name="heroicons:check-circle" class="w-5 h-5 text-candlelight-400" />
|
||||
<span>Access to all {{ totalEvents }} events in the series</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="ticket.isFree && !isMember"
|
||||
class="flex items-center gap-2 text-guild-300 dark:text-guild-300"
|
||||
>
|
||||
<Icon name="heroicons:check-circle" class="w-5 h-5 text-green-400" />
|
||||
<Icon name="heroicons:check-circle" class="w-5 h-5 text-candlelight-400" />
|
||||
<span>Automatic registration for all sessions</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="memberSavings > 0"
|
||||
class="flex items-center gap-2 text-guild-300 dark:text-guild-300"
|
||||
>
|
||||
<Icon name="heroicons:check-circle" class="w-5 h-5 text-green-400" />
|
||||
<Icon name="heroicons:check-circle" class="w-5 h-5 text-candlelight-400" />
|
||||
<span>Save {{ formatPrice(memberSavings, ticket.currency) }} as a member</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -79,9 +79,9 @@
|
|||
class="flex items-start gap-3 p-3 bg-guild-700/50 dark:bg-guild-600/30 rounded-lg"
|
||||
>
|
||||
<div
|
||||
class="w-8 h-8 rounded-full bg-purple-600/20 border border-purple-500/30 flex items-center justify-center flex-shrink-0"
|
||||
class="w-8 h-8 rounded-full bg-candlelight-600/20 border border-candlelight-500/30 flex items-center justify-center flex-shrink-0"
|
||||
>
|
||||
<span class="text-sm font-bold text-purple-300">{{ index + 1 }}</span>
|
||||
<span class="text-sm font-bold text-candlelight-300">{{ index + 1 }}</span>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="font-medium text-guild-100 dark:text-guild-100 text-sm">
|
||||
|
|
@ -104,13 +104,13 @@
|
|||
<!-- Member Benefit Callout -->
|
||||
<div
|
||||
v-if="ticket.isFree && isMember"
|
||||
class="p-4 bg-green-900/20 border border-green-700/30 rounded-lg mb-6"
|
||||
class="p-4 bg-candlelight-900/20 border border-candlelight-700/30 rounded-lg mb-6"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<Icon name="heroicons:sparkles" class="w-5 h-5 text-green-400 flex-shrink-0 mt-0.5" />
|
||||
<Icon name="heroicons:sparkles" class="w-5 h-5 text-candlelight-400 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<div class="font-semibold text-green-300 mb-1">Member Benefit</div>
|
||||
<div class="text-sm text-green-400">
|
||||
<div class="font-semibold text-candlelight-300 mb-1">Member Benefit</div>
|
||||
<div class="text-sm text-candlelight-400">
|
||||
This series pass is free for Ghost Guild members! Complete registration to secure your spot.
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -120,13 +120,13 @@
|
|||
<!-- Public vs Member Pricing -->
|
||||
<div
|
||||
v-if="!ticket.isFree && publicPrice && ticket.type === 'member'"
|
||||
class="p-4 bg-blue-900/20 border border-blue-700/30 rounded-lg mb-6"
|
||||
class="p-4 bg-candlelight-900/20 border border-candlelight-700/30 rounded-lg mb-6"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<Icon name="heroicons:tag" class="w-5 h-5 text-blue-400 flex-shrink-0 mt-0.5" />
|
||||
<Icon name="heroicons:tag" class="w-5 h-5 text-candlelight-400 flex-shrink-0 mt-0.5" />
|
||||
<div class="flex-1">
|
||||
<div class="font-semibold text-blue-300 mb-1">Member Savings</div>
|
||||
<div class="text-sm text-blue-400">
|
||||
<div class="font-semibold text-candlelight-300 mb-1">Member Savings</div>
|
||||
<div class="text-sm text-candlelight-400">
|
||||
You're saving {{ formatPrice(memberSavings, ticket.currency) }} as a member.
|
||||
Public price: {{ formatPrice(publicPrice, ticket.currency) }}
|
||||
</div>
|
||||
|
|
@ -144,13 +144,13 @@
|
|||
:name="availability.remaining > 5 ? 'heroicons:check-circle' : 'heroicons:exclamation-triangle'"
|
||||
:class="[
|
||||
'w-5 h-5',
|
||||
availability.remaining > 5 ? 'text-green-400' : 'text-yellow-400'
|
||||
availability.remaining > 5 ? 'text-candlelight-400' : 'text-ember-400'
|
||||
]"
|
||||
/>
|
||||
<span
|
||||
:class="[
|
||||
'text-sm font-medium',
|
||||
availability.remaining > 5 ? 'text-green-300' : 'text-yellow-300'
|
||||
availability.remaining > 5 ? 'text-candlelight-300' : 'text-ember-300'
|
||||
]"
|
||||
>
|
||||
{{ availability.remaining }} series pass{{ availability.remaining !== 1 ? 'es' : '' }} remaining
|
||||
|
|
@ -160,12 +160,12 @@
|
|||
|
||||
<!-- Sold Out / Waitlist -->
|
||||
<div v-if="!available" class="space-y-3">
|
||||
<div class="p-4 bg-red-900/20 border border-red-700/30 rounded-lg">
|
||||
<div class="p-4 bg-ember-900/20 border border-ember-700/30 rounded-lg">
|
||||
<div class="flex items-start gap-3">
|
||||
<Icon name="heroicons:exclamation-circle" class="w-5 h-5 text-red-400 flex-shrink-0 mt-0.5" />
|
||||
<Icon name="heroicons:exclamation-circle" class="w-5 h-5 text-ember-400 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<div class="font-semibold text-red-300 mb-1">Series Pass Sold Out</div>
|
||||
<div class="text-sm text-red-400">
|
||||
<div class="font-semibold text-ember-300 mb-1">Series Pass Sold Out</div>
|
||||
<div class="text-sm text-ember-400">
|
||||
All series passes have been claimed.
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -183,12 +183,12 @@
|
|||
</div>
|
||||
|
||||
<!-- Already Registered -->
|
||||
<div v-else-if="alreadyRegistered" class="p-4 bg-green-900/20 border border-green-700/30 rounded-lg">
|
||||
<div v-else-if="alreadyRegistered" class="p-4 bg-candlelight-900/20 border border-candlelight-700/30 rounded-lg">
|
||||
<div class="flex items-start gap-3">
|
||||
<Icon name="heroicons:check-badge" class="w-6 h-6 text-green-400 flex-shrink-0" />
|
||||
<Icon name="heroicons:check-badge" class="w-6 h-6 text-candlelight-400 flex-shrink-0" />
|
||||
<div>
|
||||
<div class="font-semibold text-green-300 mb-1">You're Registered!</div>
|
||||
<div class="text-sm text-green-400">
|
||||
<div class="font-semibold text-candlelight-300 mb-1">You're Registered!</div>
|
||||
<div class="text-sm text-candlelight-400">
|
||||
You have a series pass and are registered for all {{ totalEvents }} events.
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
<!-- Badge -->
|
||||
<div v-if="ticketInfo.isMember" class="flex-shrink-0 ml-4">
|
||||
<span
|
||||
class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400"
|
||||
class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-candlelight-900/20 text-candlelight-500 dark:bg-candlelight-900/30 dark:text-candlelight-400"
|
||||
>
|
||||
Members Only
|
||||
</span>
|
||||
|
|
@ -36,8 +36,8 @@
|
|||
<div class="mb-4">
|
||||
<div class="flex items-baseline gap-2">
|
||||
<span
|
||||
class="text-3xl font-bold"
|
||||
:class="ticketInfo.isFree ? 'text-green-400' : 'text-guild-100'"
|
||||
class="text-3xl font-bold text-ui-mono"
|
||||
:class="ticketInfo.isFree ? 'text-candlelight-400' : 'text-guild-100'"
|
||||
>
|
||||
{{ ticketInfo.formattedPrice }}
|
||||
</span>
|
||||
|
|
@ -74,9 +74,9 @@
|
|||
<!-- Member Savings -->
|
||||
<div
|
||||
v-if="ticketInfo.publicTicket && ticketInfo.memberSavings > 0"
|
||||
class="mb-4 p-3 bg-green-900/20 rounded-lg border border-green-800"
|
||||
class="mb-4 p-3 bg-candlelight-900/20 rounded-lg border border-candlelight-800"
|
||||
>
|
||||
<p class="text-sm text-green-400">
|
||||
<p class="text-sm text-candlelight-400">
|
||||
<Icon name="heroicons:check-circle" class="w-4 h-4 inline mr-1" />
|
||||
You save {{ formatPrice(ticketInfo.memberSavings) }} as a member!
|
||||
</p>
|
||||
|
|
@ -90,14 +90,14 @@
|
|||
<div>
|
||||
<span
|
||||
v-if="alreadyRegistered"
|
||||
class="text-green-400 flex items-center gap-1"
|
||||
class="text-candlelight-400 flex items-center gap-1"
|
||||
>
|
||||
<Icon name="heroicons:check-circle-solid" class="w-4 h-4" />
|
||||
You're registered
|
||||
</span>
|
||||
<span
|
||||
v-else-if="!isAvailable"
|
||||
class="text-red-400 flex items-center gap-1"
|
||||
class="text-ember-400 flex items-center gap-1"
|
||||
>
|
||||
<Icon name="heroicons:x-circle-solid" class="w-4 h-4" />
|
||||
Sold Out
|
||||
|
|
|
|||
|
|
@ -11,26 +11,26 @@
|
|||
<!-- Error State -->
|
||||
<div
|
||||
v-else-if="error"
|
||||
class="p-6 bg-red-900/20 rounded-xl border border-red-800"
|
||||
class="p-6 bg-ember-900/20 rounded-xl border border-ember-800"
|
||||
>
|
||||
<h3 class="text-lg font-semibold text-red-300 mb-2">
|
||||
<h3 class="text-lg font-semibold text-ember-300 mb-2">
|
||||
Unable to Load Tickets
|
||||
</h3>
|
||||
<p class="text-red-400">{{ error }}</p>
|
||||
<p class="text-ember-400">{{ error }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Series Pass Required -->
|
||||
<div
|
||||
v-else-if="ticketInfo?.requiresSeriesPass"
|
||||
class="p-6 bg-purple-900/20 rounded-xl border border-purple-800"
|
||||
class="p-6 bg-candlelight-900/20 rounded-xl border border-candlelight-800"
|
||||
>
|
||||
<h3
|
||||
class="text-lg font-semibold text-purple-300 mb-2 flex items-center gap-2"
|
||||
class="text-lg font-semibold text-candlelight-300 mb-2 flex items-center gap-2"
|
||||
>
|
||||
<Icon name="heroicons:ticket" class="w-6 h-6" />
|
||||
Series Pass Required
|
||||
</h3>
|
||||
<p class="text-purple-400 mb-4">
|
||||
<p class="text-candlelight-400 mb-4">
|
||||
This event is part of
|
||||
<strong>{{ ticketInfo.series?.title }}</strong> and requires a series
|
||||
pass to attend.
|
||||
|
|
@ -51,15 +51,15 @@
|
|||
<!-- Already Registered -->
|
||||
<div
|
||||
v-else-if="ticketInfo?.alreadyRegistered"
|
||||
class="p-6 bg-green-900/20 rounded-xl border border-green-800"
|
||||
class="p-6 bg-candlelight-900/20 rounded-xl border border-candlelight-800"
|
||||
>
|
||||
<h3
|
||||
class="text-lg font-semibold text-green-300 mb-2 flex items-center gap-2"
|
||||
class="text-lg font-semibold text-candlelight-300 mb-2 flex items-center gap-2"
|
||||
>
|
||||
<Icon name="heroicons:check-circle-solid" class="w-6 h-6" />
|
||||
You're Registered!
|
||||
</h3>
|
||||
<p class="text-green-400 mb-4">
|
||||
<p class="text-candlelight-400 mb-4">
|
||||
<template v-if="ticketInfo.viaSeriesPass">
|
||||
You have access to this event via your series pass for
|
||||
<strong>{{ ticketInfo.series?.title }}</strong
|
||||
|
|
@ -136,9 +136,9 @@
|
|||
<!-- Member Benefits Notice -->
|
||||
<div
|
||||
v-if="ticketInfo.isMember && ticketInfo.isFree"
|
||||
class="p-4 bg-purple-900/20 rounded-lg border border-purple-800"
|
||||
class="p-4 bg-candlelight-900/20 rounded-lg border border-candlelight-800"
|
||||
>
|
||||
<p class="text-sm text-purple-300 flex items-center gap-2">
|
||||
<p class="text-sm text-candlelight-300 flex items-center gap-2">
|
||||
<Icon name="heroicons:sparkles" class="w-4 h-4" />
|
||||
This event is free for Ghost Guild members
|
||||
</p>
|
||||
|
|
@ -147,9 +147,9 @@
|
|||
<!-- Payment Required Notice -->
|
||||
<div
|
||||
v-if="!ticketInfo.isFree"
|
||||
class="p-4 bg-blue-900/20 rounded-lg border border-blue-800"
|
||||
class="p-4 bg-candlelight-900/20 rounded-lg border border-candlelight-800"
|
||||
>
|
||||
<p class="text-sm text-blue-300 flex items-center gap-2">
|
||||
<p class="text-sm text-candlelight-300 flex items-center gap-2">
|
||||
<Icon name="heroicons:credit-card" class="w-4 h-4" />
|
||||
Payment of {{ ticketInfo.formattedPrice }} will be processed
|
||||
securely
|
||||
|
|
@ -201,7 +201,7 @@
|
|||
<div v-else-if="!ticketInfo.available" class="text-center py-8">
|
||||
<Icon
|
||||
name="heroicons:x-circle"
|
||||
class="w-16 h-16 text-red-400 mx-auto mb-4"
|
||||
class="w-16 h-16 text-ember-400 mx-auto mb-4"
|
||||
/>
|
||||
<h3 class="text-xl font-bold text-guild-100 mb-2">Event Sold Out</h3>
|
||||
<p class="text-guild-300">
|
||||
|
|
|
|||
47
app/components/GuildDivider.vue
Normal file
47
app/components/GuildDivider.vue
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
<div :class="['guild-divider', spacing]" role="separator" aria-hidden="true">
|
||||
<!-- Woodcut: irregular hand-drawn line -->
|
||||
<svg
|
||||
v-if="variant === 'woodcut'"
|
||||
viewBox="0 0 800 12"
|
||||
preserveAspectRatio="none"
|
||||
class="w-full h-3 text-guild-600"
|
||||
>
|
||||
<path
|
||||
d="M0,6 Q50,3 100,7 T200,5 T300,8 T400,4 T500,7 T600,5 T700,8 T800,6"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke-dasharray="8,3,15,4,6,5"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<!-- Dither: warm amber dithered line -->
|
||||
<div v-else-if="variant === 'dither'" class="h-px dithered-warm opacity-60" />
|
||||
|
||||
<!-- Dots: halftone dot row -->
|
||||
<div
|
||||
v-else-if="variant === 'dots'"
|
||||
class="h-2 halftone-texture opacity-30"
|
||||
/>
|
||||
|
||||
<!-- Simple: plain rule -->
|
||||
<hr v-else class="border-guild-700" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
variant: {
|
||||
type: String,
|
||||
default: 'simple',
|
||||
validator: (v) => ['simple', 'woodcut', 'dither', 'dots'].includes(v),
|
||||
},
|
||||
compact: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const spacing = computed(() => props.compact ? 'my-4' : 'my-8')
|
||||
</script>
|
||||
|
|
@ -5,14 +5,14 @@
|
|||
<img
|
||||
:src="transformedImageUrl"
|
||||
:alt="modelValue.alt || 'Event image'"
|
||||
class="w-full h-48 object-cover rounded-lg border border-neutral-200"
|
||||
class="w-full h-48 object-cover rounded-lg border border-guild-700"
|
||||
@error="console.log('Image failed to load:', transformedImageUrl)"
|
||||
@load="console.log('Image loaded successfully:', transformedImageUrl)"
|
||||
/>
|
||||
<button
|
||||
@click="removeImage"
|
||||
type="button"
|
||||
class="absolute top-2 right-2 p-1 bg-red-500 text-white rounded-full hover:bg-red-600 transition-colors"
|
||||
class="absolute top-2 right-2 p-1 bg-ember-500 text-white rounded-full hover:bg-ember-600 transition-colors"
|
||||
>
|
||||
<Icon name="heroicons:x-mark" class="w-4 h-4" />
|
||||
</button>
|
||||
|
|
@ -21,11 +21,11 @@
|
|||
<!-- Upload Area -->
|
||||
<div
|
||||
v-if="!modelValue?.url"
|
||||
class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center hover:border-gray-400 transition-colors"
|
||||
class="border-2 border-dashed border-guild-700 rounded-lg p-6 text-center hover:border-guild-600 transition-colors"
|
||||
@dragover.prevent="isDragging = true"
|
||||
@dragleave.prevent="isDragging = false"
|
||||
@drop.prevent="handleDrop"
|
||||
:class="{ 'border-blue-400 bg-blue-50': isDragging }"
|
||||
:class="{ 'border-candlelight-400 bg-candlelight-900/20': isDragging }"
|
||||
>
|
||||
<input
|
||||
ref="fileInput"
|
||||
|
|
@ -36,52 +36,52 @@
|
|||
/>
|
||||
|
||||
<div class="space-y-3">
|
||||
<Icon name="heroicons:photo" class="w-12 h-12 text-gray-400 mx-auto" />
|
||||
<Icon name="heroicons:photo" class="w-12 h-12 text-guild-400 mx-auto" />
|
||||
<div>
|
||||
<p class="text-gray-600">
|
||||
<p class="text-guild-400">
|
||||
<button
|
||||
type="button"
|
||||
@click="$refs.fileInput.click()"
|
||||
class="text-primary-600 hover:text-primary-500 font-medium"
|
||||
class="text-candlelight-400 hover:text-candlelight-300 font-medium"
|
||||
>
|
||||
Click to upload
|
||||
</button>
|
||||
or drag and drop
|
||||
</p>
|
||||
<p class="text-sm text-gray-500">PNG, JPG, GIF up to 10MB</p>
|
||||
<p class="text-sm text-guild-500">PNG, JPG, GIF up to 10MB</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alt Text Input -->
|
||||
<div v-if="modelValue?.url">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label class="block text-sm font-medium text-guild-100 mb-1">
|
||||
Alt Text (for accessibility)
|
||||
</label>
|
||||
<input
|
||||
:value="modelValue.alt || ''"
|
||||
@input="updateAltText($event.target.value)"
|
||||
placeholder="Describe this image..."
|
||||
class="w-full border border-neutral-200 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
class="w-full bg-guild-800 border border-guild-700 rounded-lg px-3 py-2 text-guild-100 placeholder-guild-500 focus:ring-2 focus:ring-candlelight-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Upload Progress -->
|
||||
<div v-if="isUploading" class="space-y-2">
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-gray-600">Uploading...</span>
|
||||
<span class="text-gray-600">{{ uploadProgress }}%</span>
|
||||
<span class="text-guild-400">Uploading...</span>
|
||||
<span class="text-guild-400">{{ uploadProgress }}%</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 rounded-full h-2">
|
||||
<div class="w-full bg-guild-800 rounded-full h-2">
|
||||
<div
|
||||
class="bg-blue-600 h-2 rounded-full transition-all duration-300"
|
||||
class="bg-candlelight-600 h-2 rounded-full transition-all duration-300"
|
||||
:style="`width: ${uploadProgress}%`"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
<div v-if="errorMessage" class="text-sm text-red-600">
|
||||
<div v-if="errorMessage" class="text-sm text-ember-400">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@
|
|||
<div class="space-y-6">
|
||||
<!-- Success State -->
|
||||
<div v-if="loginSuccess" class="text-center py-4">
|
||||
<div class="w-16 h-16 bg-green-500/20 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Icon name="heroicons:check-circle" class="w-10 h-10 text-green-400" />
|
||||
<div class="w-16 h-16 bg-candlelight-500/20 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Icon name="heroicons:check-circle" class="w-10 h-10 text-candlelight-400" />
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-guild-100 mb-2">Check your email</h3>
|
||||
<p class="text-guild-300">
|
||||
|
|
@ -55,9 +55,9 @@
|
|||
<!-- Error Message -->
|
||||
<div
|
||||
v-if="loginError"
|
||||
class="mb-4 p-3 bg-red-500/10 border border-red-500/30 rounded-lg"
|
||||
class="mb-4 p-3 bg-ember-500/10 border border-ember-500/30 rounded-lg"
|
||||
>
|
||||
<p class="text-red-400 text-sm">{{ loginError }}</p>
|
||||
<p class="text-ember-400 text-sm">{{ loginError }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
|
|
|
|||
|
|
@ -97,11 +97,11 @@ const handleActionClick = async () => {
|
|||
// Map color names to UButton color props
|
||||
const getButtonColor = (color) => {
|
||||
const colorMap = {
|
||||
orange: "orange",
|
||||
blue: "blue",
|
||||
gray: "gray",
|
||||
orange: "warning",
|
||||
blue: "primary",
|
||||
gray: "neutral",
|
||||
};
|
||||
return colorMap[color] || "blue";
|
||||
return colorMap[color] || "primary";
|
||||
};
|
||||
|
||||
// Only show banner if status is not active
|
||||
|
|
@ -117,8 +117,8 @@ const nextAction = computed(() => getNextAction());
|
|||
const getActionButtonClass = (color) => {
|
||||
const baseClass = "hover:scale-105 active:scale-95";
|
||||
const colorClasses = {
|
||||
orange: "bg-orange-600 text-white hover:bg-orange-700",
|
||||
blue: "bg-blue-600 text-white hover:bg-blue-700",
|
||||
orange: "bg-candlelight-600 text-white hover:bg-candlelight-700",
|
||||
blue: "bg-guild-600 text-white hover:bg-guild-500",
|
||||
gray: "bg-guild-700 text-guild-100 hover:bg-guild-600",
|
||||
};
|
||||
return `${baseClass} ${colorClasses[color] || colorClasses.blue}`;
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@
|
|||
<Icon
|
||||
v-if="isValidParse && naturalInput.trim()"
|
||||
name="heroicons:check-circle"
|
||||
class="w-5 h-5 text-green-500"
|
||||
class="w-5 h-5 text-candlelight-500"
|
||||
/>
|
||||
<Icon
|
||||
v-else-if="hasError && naturalInput.trim()"
|
||||
name="heroicons:exclamation-circle"
|
||||
class="w-5 h-5 text-red-500"
|
||||
class="w-5 h-5 text-ember-500"
|
||||
/>
|
||||
</template>
|
||||
</UInput>
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
<div
|
||||
v-if="parsedDate && isValidParse"
|
||||
class="text-sm text-green-700 bg-green-50 px-3 py-2 rounded-lg border border-green-200"
|
||||
class="text-sm text-candlelight-400 bg-candlelight-900/20 px-3 py-2 rounded-lg border border-candlelight-800"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<Icon name="heroicons:calendar" class="w-4 h-4" />
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
|
||||
<div
|
||||
v-if="hasError && naturalInput.trim()"
|
||||
class="text-sm text-red-700 bg-red-50 px-3 py-2 rounded-lg border border-red-200"
|
||||
class="text-sm text-ember-400 bg-ember-900/20 px-3 py-2 rounded-lg border border-ember-800"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<Icon name="heroicons:exclamation-triangle" class="w-4 h-4" />
|
||||
|
|
@ -51,7 +51,7 @@
|
|||
|
||||
<!-- Fallback datetime-local input -->
|
||||
<details class="text-sm">
|
||||
<summary class="cursor-pointer text-muted hover:text-default">
|
||||
<summary class="cursor-pointer text-guild-400 hover:text-guild-100">
|
||||
Use traditional date picker
|
||||
</summary>
|
||||
<div class="mt-2">
|
||||
|
|
|
|||
|
|
@ -1,105 +1,60 @@
|
|||
<template>
|
||||
<header
|
||||
class="relative py-16 md:py-24 bg-cover bg-center"
|
||||
style="background-image: url("/background-dither.png")"
|
||||
style="background-image: url('/background-dither.webp')"
|
||||
>
|
||||
<div class="absolute inset-0 bg-black/40"></div>
|
||||
<UContainer class="relative z-10">
|
||||
<div class="text-center max-w-4xl mx-auto">
|
||||
<h1
|
||||
class="font-bold mb-6 md:mb-8"
|
||||
:class="[titleSizeClass, titleColorClass]"
|
||||
class="font-bold mb-6 md:mb-8 text-white"
|
||||
:class="titleSizeClass"
|
||||
>
|
||||
{{ title }}
|
||||
</h1>
|
||||
|
||||
<p
|
||||
v-if="subtitle"
|
||||
class="text-lg md:text-xl leading-relaxed mb-8"
|
||||
:class="subtitleColorClass"
|
||||
class="text-lg md:text-xl leading-relaxed mb-8 text-white/90"
|
||||
>
|
||||
{{ subtitle }}
|
||||
</p>
|
||||
|
||||
<!-- Interactive Content Area (for hero sections with carousels, etc.) -->
|
||||
<!-- Interactive Content Area (for hero sections with carousels) -->
|
||||
<div
|
||||
v-if="showInteractiveArea"
|
||||
:class="[
|
||||
'rounded-2xl p-6 md:p-8 mb-12 backdrop-blur-sm',
|
||||
props.theme === 'guild'
|
||||
? 'bg-guild-800/60 border border-guild-700 candlelight-glow halftone-texture'
|
||||
: 'bg-[--ui-bg-elevated] shadow-xl border border-blue-200',
|
||||
]"
|
||||
class="rounded-2xl p-6 md:p-8 mb-12 backdrop-blur-sm bg-guild-800/60 border border-guild-700 candlelight-glow halftone-texture"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-3 md:gap-4">
|
||||
<button
|
||||
:class="[
|
||||
'p-2 md:p-3 rounded-full transition-all duration-300 flex-shrink-0',
|
||||
props.theme === 'guild'
|
||||
? 'bg-candlelight-600/80 text-guild-100 hover:bg-candlelight-500 candlelight-glow'
|
||||
: 'bg-blue-500 text-white hover:bg-blue-600',
|
||||
]"
|
||||
class="p-2 md:p-3 rounded-full transition-all duration-300 flex-shrink-0 bg-candlelight-600/80 text-guild-100 hover:bg-candlelight-500 candlelight-glow"
|
||||
@click="$emit('prev')"
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5 md:w-6 md:h-6"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 19l-7-7 7-7"
|
||||
/>
|
||||
</svg>
|
||||
<UIcon name="i-lucide-chevron-left" class="size-5 md:size-6" />
|
||||
</button>
|
||||
|
||||
<div class="text-center flex-1 min-w-0">
|
||||
<slot name="interactive-content">
|
||||
<p
|
||||
:class="[
|
||||
'text-base md:text-lg',
|
||||
props.theme === 'guild'
|
||||
? 'text-guild-200'
|
||||
: 'text-[--ui-text-muted]',
|
||||
]"
|
||||
>
|
||||
{{
|
||||
interactiveContent ||
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
}}
|
||||
<p class="text-base md:text-lg text-guild-200">
|
||||
{{ interactiveContent }}
|
||||
</p>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<button
|
||||
:class="[
|
||||
'p-2 md:p-3 rounded-full transition-all duration-300 flex-shrink-0',
|
||||
props.theme === 'guild'
|
||||
? 'bg-candlelight-600/80 text-guild-100 hover:bg-candlelight-500 candlelight-glow'
|
||||
: 'bg-blue-500 text-white hover:bg-blue-600',
|
||||
]"
|
||||
class="p-2 md:p-3 rounded-full transition-all duration-300 flex-shrink-0 bg-candlelight-600/80 text-guild-100 hover:bg-candlelight-500 candlelight-glow"
|
||||
@click="$emit('next')"
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5 md:w-6 md:h-6"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
<UIcon name="i-lucide-chevron-right" class="size-5 md:size-6" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Illustration slot for designer-provided assets -->
|
||||
<div v-if="$slots.illustration" class="mb-8">
|
||||
<slot name="illustration" />
|
||||
</div>
|
||||
|
||||
<!-- Call to Action Button -->
|
||||
<div v-if="showCta" class="flex justify-center">
|
||||
<UButton
|
||||
|
|
@ -122,8 +77,6 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
|
|
@ -131,18 +84,12 @@ const props = defineProps({
|
|||
},
|
||||
subtitle: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
default: "blue",
|
||||
validator: (value) =>
|
||||
["blue", "purple", "emerald", "gray", "guild"].includes(value),
|
||||
default: '',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: "large",
|
||||
validator: (value) => ["small", "medium", "large", "hero"].includes(value),
|
||||
default: 'large',
|
||||
validator: (value) => ['small', 'medium', 'large', 'hero'].includes(value),
|
||||
},
|
||||
showInteractiveArea: {
|
||||
type: Boolean,
|
||||
|
|
@ -150,7 +97,7 @@ const props = defineProps({
|
|||
},
|
||||
interactiveContent: {
|
||||
type: String,
|
||||
default: "",
|
||||
default: '',
|
||||
},
|
||||
showCta: {
|
||||
type: Boolean,
|
||||
|
|
@ -158,55 +105,31 @@ const props = defineProps({
|
|||
},
|
||||
ctaText: {
|
||||
type: String,
|
||||
default: "Get Started",
|
||||
default: 'Get Started',
|
||||
},
|
||||
ctaLink: {
|
||||
type: String,
|
||||
default: "/join",
|
||||
default: '/join',
|
||||
},
|
||||
ctaSize: {
|
||||
type: String,
|
||||
default: "lg",
|
||||
default: 'lg',
|
||||
},
|
||||
ctaColor: {
|
||||
type: String,
|
||||
default: "primary",
|
||||
default: 'primary',
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
defineEmits(["prev", "next"]);
|
||||
|
||||
const backgroundClass = computed(() => {
|
||||
const themes = {
|
||||
blue: "bg-gradient-to-br from-blue-50 to-indigo-100",
|
||||
purple: "bg-gradient-to-br from-purple-50 to-violet-100",
|
||||
emerald: "bg-gradient-to-br from-emerald-50 to-teal-100",
|
||||
gray: "bg-neutral-100",
|
||||
guild:
|
||||
"bg-gradient-to-br from-guild-900 via-guild-800 to-candlelight-900 halftone-texture",
|
||||
};
|
||||
return themes[props.theme] || themes.blue;
|
||||
});
|
||||
defineEmits(['prev', 'next'])
|
||||
|
||||
const titleSizeClass = computed(() => {
|
||||
const sizes = {
|
||||
small: "text-2xl md:text-3xl",
|
||||
medium: "text-3xl md:text-4xl",
|
||||
large: "text-4xl md:text-5xl",
|
||||
hero: "text-5xl md:text-6xl",
|
||||
};
|
||||
return sizes[props.size] || sizes.large;
|
||||
});
|
||||
|
||||
const titleColorClass = computed(() => {
|
||||
return "text-white";
|
||||
});
|
||||
|
||||
const subtitleColorClass = computed(() => {
|
||||
return "text-white";
|
||||
});
|
||||
|
||||
const textColorClass = computed(() => {
|
||||
return "text-white";
|
||||
});
|
||||
small: 'text-display-sm',
|
||||
medium: 'text-display',
|
||||
large: 'text-display-lg',
|
||||
hero: 'text-display-xl',
|
||||
}
|
||||
return sizes[props.size] || sizes.large
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
>
|
||||
<path
|
||||
d="M500 70 150 175.3v217.1C150 785 500 930 500 930s350-145 350-537.6V175.2L500 70Z"
|
||||
class="fill-purple-500"
|
||||
class="fill-candlelight-500"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
<!-- Sparkle effect -->
|
||||
<div
|
||||
class="absolute top-0 right-1 w-2 h-2 bg-yellow-300 rounded-full animate-pulse"
|
||||
class="absolute top-0 right-1 w-2 h-2 bg-candlelight-300 rounded-full animate-pulse"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -42,11 +42,11 @@
|
|||
:class="[
|
||||
'inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full border text-xs font-medium transition-all',
|
||||
variant === 'default' &&
|
||||
'bg-purple-500/20 text-purple-300 border-purple-500/40 hover:bg-purple-500/30',
|
||||
'bg-candlelight-900/20 text-candlelight-400 border-candlelight-500/40 hover:bg-candlelight-900/30',
|
||||
variant === 'subtle' &&
|
||||
'bg-purple-500/10 text-purple-400 border-purple-500/20',
|
||||
'bg-candlelight-900/10 text-candlelight-500 border-candlelight-500/20',
|
||||
variant === 'solid' &&
|
||||
'bg-purple-500 text-white border-purple-600 hover:bg-purple-600',
|
||||
'bg-candlelight-500 text-white border-candlelight-600 hover:bg-candlelight-600',
|
||||
]"
|
||||
:title="title"
|
||||
>
|
||||
|
|
@ -54,8 +54,8 @@
|
|||
name="heroicons:chat-bubble-left-right"
|
||||
:class="[
|
||||
'w-3.5 h-3.5',
|
||||
variant === 'default' && 'text-purple-300',
|
||||
variant === 'subtle' && 'text-purple-400',
|
||||
variant === 'default' && 'text-candlelight-400',
|
||||
variant === 'subtle' && 'text-candlelight-500',
|
||||
variant === 'solid' && 'text-white',
|
||||
]"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="flex items-center gap-3 text-sm">
|
||||
<span class="text-gray-700 dark:text-guild-400 text-xs font-medium"
|
||||
<span class="text-guild-400 text-xs font-medium"
|
||||
>{{ label }}:</span
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
|
|
@ -8,8 +8,8 @@
|
|||
class="text-xs transition-colors"
|
||||
:class="
|
||||
isPrivate
|
||||
? 'text-gray-500 dark:text-guild-500'
|
||||
: 'text-blue-600 dark:text-blue-400 font-semibold'
|
||||
? 'text-guild-500'
|
||||
: 'text-candlelight-500 font-semibold'
|
||||
"
|
||||
>
|
||||
Members
|
||||
|
|
@ -24,8 +24,8 @@
|
|||
class="text-xs transition-colors"
|
||||
:class="
|
||||
isPrivate
|
||||
? 'text-blue-600 dark:text-blue-400 font-semibold'
|
||||
: 'text-gray-500 dark:text-guild-500'
|
||||
? 'text-candlelight-500 font-semibold'
|
||||
: 'text-guild-500'
|
||||
"
|
||||
>
|
||||
Private
|
||||
|
|
@ -38,19 +38,19 @@
|
|||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "members",
|
||||
default: 'members',
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: "Privacy",
|
||||
default: 'Privacy',
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const isPrivate = computed(() => props.modelValue === "private");
|
||||
const isPrivate = computed(() => props.modelValue === 'private')
|
||||
|
||||
const togglePrivacy = (value) => {
|
||||
emit("update:modelValue", value ? "private" : "members");
|
||||
};
|
||||
emit('update:modelValue', value ? 'private' : 'members')
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
61
app/components/SectionHeader.vue
Normal file
61
app/components/SectionHeader.vue
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<div class="section-header" :class="[spacing]">
|
||||
<p v-if="overline" class="text-ui-label text-candlelight-500 mb-2">
|
||||
{{ overline }}
|
||||
</p>
|
||||
<component :is="tag" :class="[sizeClass, 'text-guild-100']">
|
||||
<slot>{{ title }}</slot>
|
||||
</component>
|
||||
<p v-if="subtitle" class="mt-3 text-guild-400 text-lg leading-relaxed">
|
||||
{{ subtitle }}
|
||||
</p>
|
||||
<GuildDivider v-if="divider" :variant="divider" compact class="mt-4" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
subtitle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
overline: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'md',
|
||||
validator: (v) => ['xl', 'lg', 'md', 'sm'].includes(v),
|
||||
},
|
||||
tag: {
|
||||
type: String,
|
||||
default: 'h2',
|
||||
},
|
||||
divider: {
|
||||
type: String,
|
||||
default: '',
|
||||
validator: (v) => ['', 'simple', 'woodcut', 'dither', 'dots'].includes(v),
|
||||
},
|
||||
compact: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const sizeClass = computed(() => {
|
||||
const sizes = {
|
||||
xl: 'text-display-xl',
|
||||
lg: 'text-display-lg',
|
||||
md: 'text-display',
|
||||
sm: 'text-display-sm',
|
||||
}
|
||||
return sizes[props.size]
|
||||
})
|
||||
|
||||
const spacing = computed(() => props.compact ? 'mb-4' : 'mb-8')
|
||||
</script>
|
||||
|
|
@ -11,12 +11,12 @@
|
|||
<!-- Error State -->
|
||||
<div
|
||||
v-else-if="error"
|
||||
class="p-6 bg-red-900/20 rounded-xl border border-red-800"
|
||||
class="p-6 bg-ember-900/20 rounded-xl border border-ember-800"
|
||||
>
|
||||
<h3 class="text-lg font-semibold text-red-300 mb-2">
|
||||
<h3 class="text-lg font-semibold text-ember-300 mb-2">
|
||||
Unable to Load Series Pass
|
||||
</h3>
|
||||
<p class="text-red-400">{{ error }}</p>
|
||||
<p class="text-ember-400">{{ error }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
|
|
@ -93,18 +93,18 @@
|
|||
<!-- Member Benefits Notice -->
|
||||
<div
|
||||
v-if="passInfo.ticket.isFree && passInfo.memberInfo?.isMember"
|
||||
class="p-4 bg-green-900/20 border border-green-700/30 rounded-lg"
|
||||
class="p-4 bg-candlelight-900/20 border border-candlelight-700/30 rounded-lg"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<Icon
|
||||
name="heroicons:sparkles"
|
||||
class="w-5 h-5 text-green-400 flex-shrink-0 mt-0.5"
|
||||
class="w-5 h-5 text-candlelight-400 flex-shrink-0 mt-0.5"
|
||||
/>
|
||||
<div>
|
||||
<div class="font-semibold text-green-300 mb-1">
|
||||
<div class="font-semibold text-candlelight-300 mb-1">
|
||||
Member Benefit
|
||||
</div>
|
||||
<div class="text-sm text-green-400">
|
||||
<div class="text-sm text-candlelight-400">
|
||||
This series pass is free for Ghost Guild members!
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
42
app/components/SvgFilters.vue
Normal file
42
app/components/SvgFilters.vue
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<!-- Hidden SVG filter definitions available to entire app -->
|
||||
<svg class="absolute w-0 h-0 overflow-hidden" aria-hidden="true">
|
||||
<defs>
|
||||
<!-- Fine grain noise - for ink-grain effect -->
|
||||
<filter id="grain-fine" x="0" y="0" width="100%" height="100%">
|
||||
<feTurbulence
|
||||
type="fractalNoise"
|
||||
baseFrequency="0.65"
|
||||
numOctaves="3"
|
||||
stitchTiles="stitch"
|
||||
result="noise"
|
||||
/>
|
||||
<feColorMatrix type="saturate" values="0" in="noise" result="mono" />
|
||||
</filter>
|
||||
|
||||
<!-- Paper fiber texture - for paper-texture effect -->
|
||||
<filter id="grain-paper" x="0" y="0" width="100%" height="100%">
|
||||
<feTurbulence
|
||||
type="fractalNoise"
|
||||
baseFrequency="0.04"
|
||||
numOctaves="5"
|
||||
stitchTiles="stitch"
|
||||
result="noise"
|
||||
/>
|
||||
<feColorMatrix type="saturate" values="0" in="noise" result="mono" />
|
||||
</filter>
|
||||
|
||||
<!-- Coarse grain - for workshop/craft surface textures -->
|
||||
<filter id="grain-coarse" x="0" y="0" width="100%" height="100%">
|
||||
<feTurbulence
|
||||
type="turbulence"
|
||||
baseFrequency="0.3"
|
||||
numOctaves="2"
|
||||
stitchTiles="stitch"
|
||||
result="noise"
|
||||
/>
|
||||
<feColorMatrix type="saturate" values="0" in="noise" result="mono" />
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
</template>
|
||||
|
|
@ -93,9 +93,9 @@
|
|||
<!-- Error Message -->
|
||||
<div
|
||||
v-if="error"
|
||||
class="bg-red-500/10 border border-red-500/30 rounded-lg p-4"
|
||||
class="bg-ember-900/20 border border-ember-400/30 rounded-lg p-4"
|
||||
>
|
||||
<p class="text-red-300">{{ error }}</p>
|
||||
<p class="text-ember-400">{{ error }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue