974 lines
28 KiB
Vue
974 lines
28 KiB
Vue
<template>
|
|
<div>
|
|
<!-- Page Header -->
|
|
<PageHeader
|
|
title="Events"
|
|
subtitle="Join our community events, workshops, and gatherings"
|
|
size="large"
|
|
/>
|
|
|
|
<!-- Events Section with Tabs -->
|
|
<section class="bg-ghost-900 dark:bg-ghost-950">
|
|
<UContainer>
|
|
<UTabs
|
|
v-model="activeTab"
|
|
:items="[
|
|
{ label: 'What\'s On', value: 'upcoming', slot: 'upcoming' },
|
|
{ label: 'Calendar', value: 'calendar', slot: 'calendar' },
|
|
]"
|
|
class="max-w-6xl mx-auto"
|
|
>
|
|
<template #upcoming>
|
|
<!-- Search and Filter Controls -->
|
|
<div class="pt-8">
|
|
<div class="max-w-4xl mx-auto mb-8">
|
|
<div class="flex flex-col gap-4">
|
|
<!-- Search Input -->
|
|
<div
|
|
class="flex flex-row flex-wrap gap-4 items-center justify-center"
|
|
>
|
|
<UInput
|
|
v-model="searchQuery"
|
|
placeholder="Search events..."
|
|
autocomplete="off"
|
|
size="xl"
|
|
class="rounded-md shadow-sm text-lg w-full md:w-2/3"
|
|
:ui="{ icon: { trailing: { pointer: '' } } }"
|
|
>
|
|
<template #trailing>
|
|
<UButton
|
|
v-show="searchQuery"
|
|
icon="i-heroicons-x-mark-20-solid"
|
|
color="gray"
|
|
variant="ghost"
|
|
@click="searchQuery = ''"
|
|
class="mr-1"
|
|
/>
|
|
</template>
|
|
</UInput>
|
|
|
|
<!-- Past Events Toggle -->
|
|
<div
|
|
class="flex flex-col items-center md:items-start gap-2"
|
|
>
|
|
<label class="text-sm font-medium text-ghost-200">
|
|
Show past events?
|
|
</label>
|
|
<USwitch v-model="includePastEvents" />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Category Filter -->
|
|
<div class="flex justify-center">
|
|
<UPopover
|
|
:ui="{ base: 'overflow-visible' }"
|
|
:popper="{
|
|
placement: 'bottom-end',
|
|
strategy: 'absolute',
|
|
}"
|
|
>
|
|
<UButton
|
|
icon="i-heroicons-adjustments-vertical-20-solid"
|
|
variant="outline"
|
|
color="primary"
|
|
size="sm"
|
|
class="rounded-md shadow-sm"
|
|
>
|
|
Filter Events
|
|
</UButton>
|
|
|
|
<template #panel="{ close }">
|
|
<div
|
|
class="bg-ghost-800 dark:bg-ghost-900 p-6 rounded-md shadow-lg w-80 space-y-4"
|
|
>
|
|
<div class="space-y-2">
|
|
<label class="text-sm font-medium text-ghost-200">
|
|
Filter by category
|
|
</label>
|
|
<USelectMenu
|
|
v-model="selectedCategories"
|
|
:options="eventCategories"
|
|
option-attribute="name"
|
|
multiple
|
|
value-attribute="id"
|
|
placeholder="Select categories..."
|
|
:ui="{
|
|
base: 'overflow-visible',
|
|
menu: { maxHeight: '200px', overflowY: 'auto' },
|
|
popper: { strategy: 'absolute' },
|
|
}"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</UPopover>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Search Status Message -->
|
|
<div v-if="isSearching" class="mt-6 text-center">
|
|
<p class="text-ghost-300 text-sm">
|
|
{{
|
|
filteredEvents.length > 0
|
|
? `Found ${filteredEvents.length} event${
|
|
filteredEvents.length !== 1 ? "s" : ""
|
|
}`
|
|
: "No events found matching your search"
|
|
}}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Events List -->
|
|
<div class="max-w-4xl mx-auto space-y-6 pb-8">
|
|
<NuxtLink
|
|
v-for="event in upcomingEvents"
|
|
:key="event.id"
|
|
:to="`/events/${event.slug || event.id}`"
|
|
class="group flex items-start gap-4 py-2 hover:opacity-80 transition-opacity"
|
|
>
|
|
<div class="flex-shrink-0 text-center">
|
|
<div class="text-2xl font-bold text-ghost-100">
|
|
{{ event.start.getDate() }}
|
|
</div>
|
|
<div class="text-xs text-ghost-400 uppercase">
|
|
{{
|
|
event.start.toLocaleDateString("en-US", {
|
|
month: "short",
|
|
})
|
|
}}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex-1 min-w-0">
|
|
<div class="flex items-start gap-2 mb-1">
|
|
<h3
|
|
class="text-lg font-semibold text-ghost-100 group-hover:text-primary transition-colors"
|
|
>
|
|
{{ event.title }}
|
|
</h3>
|
|
<Icon
|
|
v-if="event.membersOnly"
|
|
name="heroicons:lock-closed"
|
|
class="w-4 h-4 text-purple-500 flex-shrink-0 mt-1"
|
|
/>
|
|
</div>
|
|
|
|
<p class="text-sm text-ghost-300 mb-2 line-clamp-2">
|
|
{{ event.content }}
|
|
</p>
|
|
|
|
<div v-if="event.series?.isSeriesEvent" class="mt-2">
|
|
<EventSeriesBadge
|
|
:title="event.series.title"
|
|
:position="event.series.position"
|
|
:total-events="event.series.totalEvents"
|
|
:series-id="event.series.id"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<Icon
|
|
name="heroicons:arrow-right"
|
|
class="w-5 h-5 text-ghost-400 group-hover:text-primary group-hover:translate-x-1 transition-all flex-shrink-0 mt-1"
|
|
/>
|
|
</NuxtLink>
|
|
</div>
|
|
</template>
|
|
|
|
<template #calendar>
|
|
<div class="pt-8 pb-8">
|
|
<div class="max-w-6xl mx-auto" id="event-calendar">
|
|
<ClientOnly>
|
|
<div
|
|
v-if="pending"
|
|
class="min-h-[400px] bg-ghost-800 rounded-xl flex items-center justify-center"
|
|
>
|
|
<div class="text-center">
|
|
<div
|
|
class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"
|
|
></div>
|
|
<p class="text-ghost-200">Loading calendar...</p>
|
|
</div>
|
|
</div>
|
|
<div v-else style="min-height: 600px">
|
|
<VueCal
|
|
ref="vuecal"
|
|
:events="calendarEvents"
|
|
:time="false"
|
|
active-view="month"
|
|
class="ghost-calendar"
|
|
:disable-views="['years', 'year']"
|
|
:hide-view-selector="false"
|
|
events-on-month-view="short"
|
|
today-button
|
|
@event-click="onEventClick"
|
|
>
|
|
</VueCal>
|
|
</div>
|
|
<template #fallback>
|
|
<div
|
|
class="min-h-[400px] bg-ghost-800 rounded-xl flex items-center justify-center"
|
|
>
|
|
<div class="text-center">
|
|
<div
|
|
class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"
|
|
></div>
|
|
<p class="text-ghost-200">Loading calendar...</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</ClientOnly>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</UTabs>
|
|
</UContainer>
|
|
</section>
|
|
|
|
<!-- Event Series -->
|
|
<div v-if="activeSeries.length > 0" class="text-center my-12">
|
|
<h2 class="text-3xl font-bold text-ghost-100 mb-8">
|
|
Current Event Series
|
|
</h2>
|
|
</div>
|
|
|
|
<div
|
|
v-if="activeSeries.length > 0"
|
|
class="space-y-6 max-w-6xl mx-auto mb-20"
|
|
>
|
|
<NuxtLink
|
|
v-for="series in activeSeries.slice(0, 6)"
|
|
:key="series.id"
|
|
:to="`/series/${series.id}`"
|
|
class="series-list-item block bg-ghost-800/50 dark:bg-ghost-700/30 rounded-xl p-6 border border-ghost-600 dark:border-ghost-600 hover:border-ghost-500 hover:bg-ghost-800/70 dark:hover:bg-ghost-700/50 transition-all duration-300"
|
|
>
|
|
<div class="flex items-start justify-between mb-4">
|
|
<div class="flex items-center gap-2">
|
|
<span
|
|
class="series-list-item__label text-sm font-semibold text-ghost-300 dark:text-ghost-300"
|
|
>
|
|
Event Series
|
|
</span>
|
|
<span
|
|
class="series-list-item__count inline-flex items-center px-2 py-0.5 rounded-md bg-ghost-700/50 dark:bg-ghost-600/50 text-sm font-medium text-ghost-200 dark:text-ghost-200"
|
|
>
|
|
{{ series.eventCount }} events
|
|
</span>
|
|
</div>
|
|
<span
|
|
:class="[
|
|
'series-list-item__status inline-flex items-center px-2 py-1 rounded-full text-xs font-medium',
|
|
series.status === 'active'
|
|
? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
|
|
: series.status === 'upcoming'
|
|
? 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
|
|
: 'bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400',
|
|
]"
|
|
>
|
|
{{ series.status }}
|
|
</span>
|
|
</div>
|
|
|
|
<h3
|
|
class="series-list-item__title text-lg font-semibold text-ghost-100 dark:text-ghost-100 mb-2"
|
|
>
|
|
{{ series.title }}
|
|
</h3>
|
|
|
|
<p
|
|
class="series-list-item__description text-sm text-ghost-300 dark:text-ghost-300 mb-4 line-clamp-2"
|
|
>
|
|
{{ series.description }}
|
|
</p>
|
|
|
|
<div class="series-list-item__events space-y-2 mb-4">
|
|
<div
|
|
v-for="(event, index) in series.events.slice(0, 3)"
|
|
:key="index"
|
|
class="series-list-item__event flex items-center justify-between text-xs"
|
|
>
|
|
<div class="flex items-center gap-2">
|
|
<div
|
|
class="series-list-item__event-number w-6 h-6 bg-ghost-700/50 dark:bg-ghost-600/50 text-ghost-200 dark:text-ghost-200 rounded-full flex items-center justify-center text-xs font-medium border border-ghost-600 dark:border-ghost-500"
|
|
>
|
|
{{ event.series?.position || index + 1 }}
|
|
</div>
|
|
<span
|
|
class="series-list-item__event-title text-ghost-200 dark:text-ghost-200 truncate"
|
|
>{{ event.title }}</span
|
|
>
|
|
</div>
|
|
<span
|
|
class="series-list-item__event-date text-ghost-300 dark:text-ghost-300"
|
|
>
|
|
{{ formatEventDate(event.startDate) }}
|
|
</span>
|
|
</div>
|
|
<div
|
|
v-if="series.events.length > 3"
|
|
class="series-list-item__more-events text-xs text-ghost-300 dark:text-ghost-300 text-center pt-1"
|
|
>
|
|
+{{ series.events.length - 3 }} more events
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
class="series-list-item__date-range text-sm text-ghost-300 dark:text-ghost-300"
|
|
>
|
|
{{ formatDateRange(series.startDate, series.endDate) }}
|
|
</div>
|
|
</NuxtLink>
|
|
</div>
|
|
|
|
<!-- Attend Our Events -->
|
|
<section class="py-20 bg-ghost-800 dark:bg-ghost-900">
|
|
<UContainer>
|
|
<div class="text-center mb-16">
|
|
<h2 class="text-3xl font-bold text-ghost-100 mb-8">
|
|
Attend Our Events
|
|
</h2>
|
|
</div>
|
|
|
|
<div class="max-w-4xl mx-auto">
|
|
<div
|
|
class="bg-ghost-900 rounded-2xl p-8 border border-ghost-700 mb-12"
|
|
>
|
|
<div class="prose prose-lg dark:prose-invert max-w-none">
|
|
<p class="text-lg leading-relaxed text-ghost-200 mb-6">
|
|
Our events bring together game developers, founders, and practitioners
|
|
who are building more equitable workplaces. From hands-on workshops
|
|
about governance and finance to casual co-working sessions and game nights,
|
|
there's something for every stage of your journey.
|
|
</p>
|
|
|
|
<p class="text-lg leading-relaxed text-ghost-200 mb-6">
|
|
All events are designed to be accessible, with most offered free to members
|
|
and sliding-scale pricing for non-members. Can't make it live?
|
|
Many sessions are recorded and shared in our resource library.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
|
<div class="text-center">
|
|
<h3 class="text-lg font-semibold text-ghost-100 mb-2">
|
|
Monthly Meetups
|
|
</h3>
|
|
|
|
<p class="text-sm text-ghost-300">
|
|
Casual knowledge sharing sessions
|
|
</p>
|
|
</div>
|
|
|
|
<div class="text-center">
|
|
<h3 class="text-lg font-semibold text-ghost-100 mb-2">
|
|
Workshops
|
|
</h3>
|
|
|
|
<p class="text-sm text-ghost-300">
|
|
Hands-on learning about cooperative and worker-centric business
|
|
models
|
|
</p>
|
|
</div>
|
|
|
|
<div class="text-center">
|
|
<h3 class="text-lg font-semibold text-ghost-100 mb-2">
|
|
Social Events
|
|
</h3>
|
|
<p class="text-sm text-ghost-300">
|
|
Game nights, socials, and more
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</UContainer>
|
|
</section>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { VueCal } from "vue-cal";
|
|
import "vue-cal/style.css";
|
|
|
|
// Composables
|
|
const { formatDateRange, formatDate, isPastDate } = useEventDateUtils();
|
|
const {
|
|
searchQuery,
|
|
includePastEvents,
|
|
searchResults,
|
|
selectedCategories,
|
|
isSearching,
|
|
saveSearchState,
|
|
loadSearchState,
|
|
clearSearch: clearSearchFilters,
|
|
} = useCalendarSearch();
|
|
|
|
// Active tab state
|
|
const activeTab = ref("upcoming");
|
|
|
|
// Hover state for calendar cells
|
|
const hoveredCells = ref({});
|
|
|
|
const handleMouseEnter = (date) => {
|
|
hoveredCells.value[date] = true;
|
|
};
|
|
|
|
const handleMouseLeave = (date) => {
|
|
hoveredCells.value[date] = false;
|
|
};
|
|
|
|
const isHovered = (date) => {
|
|
return hoveredCells.value[date] || false;
|
|
};
|
|
|
|
// Fetch events from API
|
|
const { data: eventsData, pending, error } = await useFetch("/api/events");
|
|
// Fetch series from API
|
|
const { data: seriesData } = await useFetch("/api/series");
|
|
|
|
// Event categories for filtering
|
|
const eventCategories = computed(() => {
|
|
if (!eventsData.value) return [];
|
|
const categories = new Set();
|
|
eventsData.value.forEach((event) => {
|
|
if (event.eventType) {
|
|
categories.add(event.eventType);
|
|
}
|
|
});
|
|
return Array.from(categories).map((type) => ({
|
|
id: type,
|
|
name: type.charAt(0).toUpperCase() + type.slice(1),
|
|
}));
|
|
});
|
|
|
|
// Transform events for calendar display
|
|
const events = computed(() => {
|
|
if (!eventsData.value) {
|
|
return [];
|
|
}
|
|
|
|
const transformed = eventsData.value.map((event) => ({
|
|
id: event.id || event._id,
|
|
slug: event.slug,
|
|
start: new Date(event.startDate),
|
|
end: new Date(event.endDate),
|
|
title: event.title,
|
|
content: event.description,
|
|
class: `event-${event.eventType}`,
|
|
membersOnly: event.membersOnly,
|
|
eventType: event.eventType,
|
|
location: event.location,
|
|
registeredCount: event.registeredCount,
|
|
maxAttendees: event.maxAttendees,
|
|
featureImage: event.featureImage,
|
|
series: event.series,
|
|
}));
|
|
return transformed;
|
|
});
|
|
|
|
// Helper function to format date for VueCal
|
|
// When time is disabled, VueCal expects just 'YYYY-MM-DD' format
|
|
const formatDateForVueCal = (date) => {
|
|
const d = date instanceof Date ? date : new Date(date);
|
|
const year = d.getFullYear();
|
|
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
const day = String(d.getDate()).padStart(2, "0");
|
|
return `${year}-${month}-${day}`;
|
|
};
|
|
|
|
// Transform events for calendar view
|
|
const calendarEvents = computed(() => {
|
|
const filtered = events.value
|
|
.filter((event) => {
|
|
// Filter by past events setting
|
|
if (!includePastEvents.value && isPastDate(event.start)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
})
|
|
.map((event) => {
|
|
// VueCal expects Date objects when time is disabled
|
|
// Only pass title (no content/description) for clean display
|
|
return {
|
|
start: event.start, // Use Date object directly
|
|
end: event.end, // Use Date object directly
|
|
title: event.title, // Only title, no description
|
|
class: event.class,
|
|
};
|
|
});
|
|
|
|
return filtered;
|
|
});
|
|
|
|
// Filter events based on search query and selected categories
|
|
const filteredEvents = computed(() => {
|
|
return events.value.filter((event) => {
|
|
// Filter by past events setting
|
|
if (!includePastEvents.value && isPastDate(event.start)) {
|
|
return false;
|
|
}
|
|
|
|
// Filter by search query
|
|
if (searchQuery.value) {
|
|
const query = searchQuery.value.toLowerCase();
|
|
const matchesTitle = event.title.toLowerCase().includes(query);
|
|
const matchesDescription =
|
|
event.content?.toLowerCase().includes(query) || false;
|
|
if (!matchesTitle && !matchesDescription) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Filter by selected categories
|
|
if (selectedCategories.value.length > 0) {
|
|
if (!selectedCategories.value.includes(event.eventType)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
});
|
|
});
|
|
|
|
// Get active event series
|
|
const activeSeries = computed(() => {
|
|
if (!seriesData.value) return [];
|
|
return seriesData.value.filter(
|
|
(series) =>
|
|
series.status === "active" || series.isOngoing || series.isUpcoming,
|
|
);
|
|
});
|
|
|
|
// Get upcoming events (future events, respecting filters)
|
|
const upcomingEvents = computed(() => {
|
|
const now = new Date();
|
|
return filteredEvents.value
|
|
.filter((event) => event.start > now)
|
|
.sort((a, b) => a.start - b.start)
|
|
.slice(0, 10); // Show max 10 upcoming events
|
|
});
|
|
|
|
// Format event date for display
|
|
const formatEventDate = (date) => {
|
|
const dateObj = date instanceof Date ? date : new Date(date);
|
|
return new Intl.DateTimeFormat("en-US", {
|
|
month: "short",
|
|
day: "numeric",
|
|
year: "numeric",
|
|
}).format(dateObj);
|
|
};
|
|
|
|
// Get optimized Cloudinary image URL
|
|
const getOptimizedImageUrl = (publicId, transformations) => {
|
|
if (!publicId) return "";
|
|
|
|
const config = useRuntimeConfig();
|
|
return `https://res.cloudinary.com/${config.public.cloudinaryCloudName}/image/upload/${transformations}/f_auto,q_auto/${publicId}`;
|
|
};
|
|
|
|
// Get image URL with fallback logic
|
|
const getImageUrl = (featureImage) => {
|
|
if (!featureImage) return "";
|
|
|
|
// If we have a direct URL, use it as primary (since seed data uses external URLs)
|
|
if (featureImage.url) {
|
|
return featureImage.url;
|
|
}
|
|
|
|
// Fallback to Cloudinary if we have a publicId
|
|
if (featureImage.publicId) {
|
|
return getOptimizedImageUrl(featureImage.publicId, "w_400,h_200,c_fill");
|
|
}
|
|
|
|
return "";
|
|
};
|
|
|
|
// Handle image loading errors
|
|
const handleImageError = (event) => {
|
|
console.warn("Image failed to load:", event.target.src);
|
|
// Optionally hide the image container or show a placeholder
|
|
};
|
|
|
|
// Handle calendar event click
|
|
const onEventClick = (event) => {
|
|
if (event.id) {
|
|
navigateTo(`/events/${event.slug || event.id}`);
|
|
}
|
|
};
|
|
|
|
// Series helper functions
|
|
const formatSeriesType = (type) => {
|
|
const types = {
|
|
workshop_series: "Workshop Series",
|
|
recurring_meetup: "Recurring Meetup",
|
|
multi_day: "Multi-Day Event",
|
|
course: "Course",
|
|
tournament: "Tournament",
|
|
};
|
|
return types[type] || type;
|
|
};
|
|
|
|
const getSeriesTypeBadgeClass = (type) => {
|
|
const classes = {
|
|
workshop_series:
|
|
"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400",
|
|
recurring_meetup:
|
|
"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400",
|
|
multi_day:
|
|
"bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400",
|
|
course:
|
|
"bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400",
|
|
tournament: "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400",
|
|
};
|
|
return (
|
|
classes[type] ||
|
|
"bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400"
|
|
);
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.line-clamp-2 {
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Calendar styling based on tranzac design principles */
|
|
.ghost-calendar :deep(.vuecal__event) {
|
|
border-radius: 0.5rem;
|
|
border: 2px solid #292524;
|
|
transform: translateZ(0);
|
|
transition: transform 300ms;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__event:hover) {
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__event-title) {
|
|
font-weight: 700;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__event-time) {
|
|
display: none;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__event.event-community) {
|
|
background-color: #2563eb;
|
|
color: #f5f5f4;
|
|
border-color: #1d4ed8;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__event.event-workshop) {
|
|
background-color: #059669;
|
|
color: #f5f5f4;
|
|
border-color: #047857;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__event.event-social) {
|
|
background-color: #7c3aed;
|
|
color: #f5f5f4;
|
|
border-color: #6d28d9;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__event.event-showcase) {
|
|
background-color: #d97706;
|
|
color: #f5f5f4;
|
|
border-color: #b45309;
|
|
}
|
|
|
|
#event-calendar {
|
|
.vuecal__cell-events-count {
|
|
position: absolute;
|
|
top: 0.5rem;
|
|
right: 0.25rem;
|
|
left: auto;
|
|
background-color: transparent;
|
|
color: #f5f5f4;
|
|
font-weight: 700;
|
|
font-size: 1.25rem;
|
|
}
|
|
|
|
.month-view .vuecal__cell--today,
|
|
.vuecal__cell--today {
|
|
background-color: rgba(59, 130, 246, 0.15);
|
|
color: #f5f5f4;
|
|
border: 2px solid #3b82f6 !important;
|
|
|
|
.day-of-month {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.day-of-month::before {
|
|
content: "Today";
|
|
text-transform: uppercase;
|
|
font-size: 0.75rem;
|
|
margin-right: 0.5rem;
|
|
display: none;
|
|
color: #3b82f6;
|
|
font-weight: 600;
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.day-of-month::before {
|
|
display: block;
|
|
}
|
|
}
|
|
}
|
|
|
|
.vuecal__cell--selected {
|
|
background-color: transparent;
|
|
}
|
|
|
|
.past-date {
|
|
background-color: rgba(120, 113, 108, 0.1);
|
|
color: #78716c;
|
|
}
|
|
|
|
.vuecal__cell--has-events {
|
|
}
|
|
|
|
.vuecal__cell--disabled {
|
|
}
|
|
|
|
.vuecal__cell-events {
|
|
}
|
|
|
|
.vuecal__event {
|
|
border-radius: 0.5rem;
|
|
border: 2px solid #3a3a3a;
|
|
transform: translateZ(0);
|
|
transition: transform 300ms;
|
|
|
|
&:hover {
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
&.vuecal__event--selected {
|
|
background-color: #2a2a2a;
|
|
}
|
|
|
|
.vuecal__cell--today {
|
|
background-color: transparent;
|
|
}
|
|
|
|
.vuecal__event-title {
|
|
font-weight: 700;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
color: #f5f5f4;
|
|
}
|
|
|
|
.vuecal__event-time {
|
|
font-weight: 400;
|
|
color: #a8a29e;
|
|
}
|
|
|
|
&.event-community {
|
|
background-color: #2563eb;
|
|
color: #f5f5f4;
|
|
border-color: #1d4ed8;
|
|
}
|
|
|
|
&.event-workshop {
|
|
background-color: #059669;
|
|
color: #f5f5f4;
|
|
border-color: #047857;
|
|
}
|
|
|
|
&.event-social {
|
|
background-color: #7c3aed;
|
|
color: #f5f5f4;
|
|
border-color: #6d28d9;
|
|
}
|
|
|
|
&.event-showcase {
|
|
background-color: #d97706;
|
|
color: #f5f5f4;
|
|
border-color: #b45309;
|
|
}
|
|
}
|
|
|
|
.vuecal__cell.vuecal__cell--out-of-scope {
|
|
pointer-events: none;
|
|
cursor: not-allowed;
|
|
border: none;
|
|
}
|
|
|
|
.outOfScope {
|
|
height: 100%;
|
|
width: 100%;
|
|
position: absolute;
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
pointer-events: none;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
:not(.vuecal__cell--out-of-scope) .vuecal__cell {
|
|
border: 1px solid #3a3a3a;
|
|
}
|
|
|
|
.vuecal__cell:before {
|
|
border: none;
|
|
}
|
|
|
|
.vuecal__title-bar {
|
|
margin-top: 1rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
}
|
|
|
|
/* Ghost calendar theme */
|
|
.ghost-calendar {
|
|
--vuecal-primary-color: #fff;
|
|
--vuecal-text-color: #e7e5e4;
|
|
--vuecal-border-color: #57534e;
|
|
--vuecal-header-color: #1c1917;
|
|
--vuecal-today-color: #292524;
|
|
background-color: #292524;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__bg) {
|
|
background-color: #292524;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__header) {
|
|
background-color: #1c1917;
|
|
border-bottom: 1px solid #57534e;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__title-bar) {
|
|
background-color: #1c1917;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__title) {
|
|
color: #e7e5e4;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__weekdays-headings) {
|
|
background-color: #1c1917;
|
|
border-bottom: 1px solid #57534e;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__heading) {
|
|
color: #a8a29e;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__cell) {
|
|
background-color: #292524;
|
|
border-color: #57534e;
|
|
color: #e7e5e4;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__cell:hover) {
|
|
background-color: #44403c;
|
|
border-color: #78716c;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__cell-content) {
|
|
color: #e7e5e4;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__cell--today) {
|
|
background-color: rgba(59, 130, 246, 0.1);
|
|
border: 2px solid #3b82f6;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__cell--out-of-scope) {
|
|
background-color: #1c1917;
|
|
color: #78716c;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__arrow) {
|
|
color: #a8a29e;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__arrow:hover) {
|
|
background-color: #44403c;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__today-btn) {
|
|
background-color: #44403c;
|
|
color: #fff;
|
|
border: 1px solid #78716c;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__today-btn:hover) {
|
|
background-color: #57534e;
|
|
border-color: #a8a29e;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__view-btn),
|
|
.ghost-calendar :deep(button[class*="view"]) {
|
|
background-color: #44403c !important;
|
|
color: #ffffff !important;
|
|
border: 1px solid #78716c !important;
|
|
font-weight: 600 !important;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__view-btn:hover),
|
|
.ghost-calendar :deep(button[class*="view"]:hover) {
|
|
background-color: #57534e !important;
|
|
border-color: #a8a29e !important;
|
|
color: #ffffff !important;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__view-btn--active),
|
|
.ghost-calendar :deep(button[class*="view"][class*="active"]) {
|
|
background-color: #0c0a09 !important;
|
|
color: #ffffff !important;
|
|
border-color: #a8a29e !important;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__view-btn--active:hover),
|
|
.ghost-calendar :deep(button[class*="view"][class*="active"]:hover) {
|
|
background-color: #1c1917 !important;
|
|
border-color: #d6d3d1 !important;
|
|
color: #ffffff !important;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__title-bar button) {
|
|
color: #ffffff !important;
|
|
font-weight: 600 !important;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__title-bar .default-view-btn) {
|
|
background-color: #44403c !important;
|
|
color: #ffffff !important;
|
|
border: 1px solid #78716c !important;
|
|
}
|
|
|
|
.ghost-calendar :deep(.vuecal__title-bar .default-view-btn.active) {
|
|
background-color: #0c0a09 !important;
|
|
border-color: #a8a29e !important;
|
|
}
|
|
|
|
/* Responsive calendar */
|
|
.vuecal {
|
|
border-radius: 0.75rem;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.vuecal__header {
|
|
background-color: var(--vuecal-header-color);
|
|
color: var(--vuecal-text-color);
|
|
}
|
|
|
|
.vuecal__title {
|
|
color: var(--vuecal-primary-color);
|
|
font-weight: 600;
|
|
}
|
|
</style>
|