mirror of
https://github.com/ArthurDanjou/artchat.git
synced 2026-01-14 15:54:03 +01:00
- Implemented InfiniteCanvas.vue for rendering an infinite canvas with drag and zoom capabilities. - Created useInfiniteCanvas composable for managing canvas state and interactions. - Added useImagePreloader composable for preloading images and videos. - Introduced constants for physics, touch interactions, viewport settings, and zoom defaults. - Developed utility functions for touch handling and media type detection. - Defined TypeScript types for canvas items, grid items, and composables. - Registered components and composables in the Nuxt module. - Added screenshot generation functionality for content files. - Updated package.json to include capture-website dependency.
117 lines
3.3 KiB
Vue
117 lines
3.3 KiB
Vue
<script setup lang="ts">
|
|
import type { LoaderProps } from '../types'
|
|
|
|
const props = withDefaults(defineProps<LoaderProps>(), {
|
|
title: 'Loading Canvas',
|
|
description: 'Preparing your visual journey...',
|
|
})
|
|
|
|
const RADIUS = 45
|
|
const CIRCUMFERENCE = 2 * Math.PI * RADIUS
|
|
|
|
/**
|
|
* Calculate progress percentage for display
|
|
*/
|
|
const percent = computed(() => Math.round(props.progress * 100))
|
|
|
|
/**
|
|
* Calculate stroke dash offset for circular progress
|
|
*/
|
|
const dashOffset = computed(() => CIRCUMFERENCE * (1 - props.progress))
|
|
|
|
/**
|
|
* Animation props for the content reveal
|
|
*/
|
|
const contentAnimation = {
|
|
initial: { opacity: 0, y: 20 },
|
|
animate: { opacity: 1, y: 0 },
|
|
transition: { duration: 0.5, delay: 0.2 },
|
|
}
|
|
|
|
/**
|
|
* Check if loading is complete
|
|
*/
|
|
const isComplete = computed(() => props.progress >= 1)
|
|
</script>
|
|
|
|
<template>
|
|
<Transition
|
|
enter-active-class="transition-all duration-500 ease-out"
|
|
leave-active-class="transition-all duration-700 ease-out"
|
|
enter-from-class="opacity-0"
|
|
enter-to-class="opacity-100"
|
|
leave-from-class="opacity-100"
|
|
leave-to-class="opacity-0"
|
|
>
|
|
<div
|
|
v-if="isVisible"
|
|
class="fixed inset-0 z-50 flex items-center justify-center bg-default"
|
|
>
|
|
<div class="pointer-events-none fixed inset-0 size-full overflow-hidden">
|
|
<div class="noise pointer-events-none absolute inset-[-200%] size-[400%] bg-[url('/noise.png')] opacity-[4%]" />
|
|
</div>
|
|
|
|
<div class="relative flex flex-col items-center space-y-8">
|
|
<div class="relative">
|
|
<svg
|
|
class="size-24 -rotate-90 transform"
|
|
viewBox="0 0 100 100"
|
|
>
|
|
<circle
|
|
cx="50"
|
|
cy="50"
|
|
:r="RADIUS"
|
|
fill="none"
|
|
stroke="rgba(255, 255, 255, 0.1)"
|
|
stroke-width="2"
|
|
/>
|
|
<circle
|
|
cx="50"
|
|
cy="50"
|
|
:r="RADIUS"
|
|
fill="none"
|
|
stroke="rgba(255, 255, 255, 0.8)"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
:stroke-dasharray="CIRCUMFERENCE"
|
|
:stroke-dashoffset="dashOffset"
|
|
class="transition-all duration-300 ease-out"
|
|
/>
|
|
</svg>
|
|
|
|
<div class="absolute inset-0 flex items-center justify-center">
|
|
<span class="text-xl font-medium text-highlighted">
|
|
{{ percent }}%
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="text-center">
|
|
<Motion v-bind="contentAnimation">
|
|
<h2 class="text-2xl font-light text-highlighted mb-2">
|
|
{{ title }}
|
|
</h2>
|
|
<p class="text-sm text-highlighted/60">
|
|
{{ description }}
|
|
</p>
|
|
</Motion>
|
|
</div>
|
|
|
|
<div class="flex space-x-2">
|
|
<div
|
|
v-for="i in 3"
|
|
:key="i"
|
|
class="size-2 rounded-full bg-inverted/40"
|
|
:class="{ 'animate-pulse': !isComplete }"
|
|
:style="{ animationDelay: `${i * 0.2}s` }"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="pointer-events-none absolute inset-0">
|
|
<div class="absolute left-1/2 top-1/2 size-96 -translate-x-1/2 -translate-y-1/2 rounded-full bg-inverted/5 blur-3xl" />
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
</template>
|