mirror of
https://github.com/ArthurDanjou/artsite.git
synced 2026-01-14 15:54:13 +01:00
Refactor le composant AppBackground pour utiliser un dégradé de fond basé sur les couleurs de navigation et le mode de couleur, en supprimant l'ancienne logique de points animés.
This commit is contained in:
@@ -1,47 +1,31 @@
|
||||
<script lang="ts" setup>
|
||||
const points = useState(() => Array.from({ length: 45 }).fill(0).map(() => [Math.random(), Math.random()]))
|
||||
const poly = computed(() => points.value.map(([x, y]) => `${x! * 100}% ${y! * 100}%`).join(', '))
|
||||
|
||||
function jumpVal(val: number) {
|
||||
return Math.random() > 0.5 ? val + (Math.random() - 0.5) / 2 : Math.random()
|
||||
}
|
||||
|
||||
let timeout: NodeJS.Timeout
|
||||
function jumpPoints() {
|
||||
for (let i = 0; i < points.value.length; i++)
|
||||
points.value[i] = [jumpVal(points.value[i][0]), jumpVal(points.value[i][1])]
|
||||
|
||||
timeout = setTimeout(jumpPoints, Math.random() * 1000)
|
||||
}
|
||||
|
||||
onMounted(() => jumpPoints())
|
||||
onUnmounted(() => clearTimeout(timeout))
|
||||
import { navColors } from '~~/types'
|
||||
|
||||
const route = useRoute()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const backgroundStyle = computed(() => {
|
||||
const colors = navColors.find(nav => nav.label === route.path)!.colors
|
||||
const fallback = colorMode.value === 'dark' ? '#000000' : '#ffffff'
|
||||
|
||||
const currentColors = colors || [fallback, fallback]
|
||||
|
||||
return {
|
||||
colorOne: currentColors[0],
|
||||
colorTwo: currentColors[1],
|
||||
backgroundImage: `
|
||||
radial-gradient(circle 500px at ${colorOne.x}% ${colorOne.y}%, ${colorOne.color}4D, transparent),
|
||||
radial-gradient(circle 500px at ${colorTwo.x}% ${colorTwo.y}%, ${colorTwo.color}4D, transparent)
|
||||
`,
|
||||
backgroundSize: '100% 100%',
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<div
|
||||
v-show="route.path === '/'"
|
||||
aria-hidden="true"
|
||||
class="duration-300 bg sm:mx-8 absolute inset-0 z-20 transform-gpu blur-3xl overflow-hidden"
|
||||
>
|
||||
<div
|
||||
:style="{ 'clip-path': `polygon(${poly})` }"
|
||||
class="aspect-[2] h-full w-full bg-gradient-to-r from-neutral-400 dark:from-neutral-600 to-white/10 lg:opacity-30 xs:opacity-50"
|
||||
/>
|
||||
<div class="min-h-screen w-full absolute inset-0">
|
||||
<div class="absolute inset-0 z-0" :style="backgroundStyle" />
|
||||
</div>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.bg > div {
|
||||
clip-path: circle(75%);
|
||||
transition: clip-path 3s;
|
||||
}
|
||||
|
||||
.light .bg > div {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,101 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { socials } from '~~/types'
|
||||
|
||||
const colorMode = useColorMode()
|
||||
const nextTheme = computed(() => (colorMode.value === 'dark' ? 'light' : 'dark'))
|
||||
|
||||
function switchTheme() {
|
||||
colorMode.preference = nextTheme.value
|
||||
}
|
||||
|
||||
function startViewTransition(event: MouseEvent | { clientX: number, clientY: number }) {
|
||||
if (!document.startViewTransition) {
|
||||
switchTheme()
|
||||
return
|
||||
}
|
||||
|
||||
const x = event.clientX
|
||||
const y = event.clientY
|
||||
const endRadius = Math.hypot(
|
||||
Math.max(x, window.innerWidth - x),
|
||||
Math.max(y, window.innerHeight - y),
|
||||
)
|
||||
|
||||
const transition = document.startViewTransition(async () => {
|
||||
switchTheme()
|
||||
await nextTick()
|
||||
})
|
||||
|
||||
transition.ready.then(() => {
|
||||
const clipPath = [
|
||||
`circle(0px at ${x}px ${y}px)`,
|
||||
`circle(${endRadius}px at ${x}px ${y}px)`,
|
||||
]
|
||||
document.documentElement.animate(
|
||||
{
|
||||
clipPath: colorMode.value === 'dark'
|
||||
? [...clipPath].reverse()
|
||||
: clipPath,
|
||||
},
|
||||
{
|
||||
duration: 500,
|
||||
easing: 'ease-out',
|
||||
pseudoElement: colorMode.value === 'dark'
|
||||
? '::view-transition-old(root)'
|
||||
: '::view-transition-new(root)',
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const navs = [
|
||||
{
|
||||
label: {
|
||||
en: 'home',
|
||||
fr: 'accueil',
|
||||
es: 'inicio',
|
||||
},
|
||||
to: '/',
|
||||
icon: 'house-duotone',
|
||||
},
|
||||
{
|
||||
label: {
|
||||
en: 'uses',
|
||||
fr: 'usages',
|
||||
es: 'usos',
|
||||
},
|
||||
to: '/uses',
|
||||
icon: 'backpack-duotone',
|
||||
},
|
||||
{
|
||||
label: {
|
||||
en: 'writings',
|
||||
fr: 'écrits',
|
||||
es: 'escritos',
|
||||
},
|
||||
to: '/writings',
|
||||
icon: 'books-duotone',
|
||||
},
|
||||
{
|
||||
label: {
|
||||
en: 'projects',
|
||||
fr: 'projets',
|
||||
es: 'proyectos',
|
||||
},
|
||||
to: '/projects',
|
||||
icon: 'code-duotone',
|
||||
},
|
||||
{
|
||||
label: {
|
||||
en: 'resume',
|
||||
fr: 'cv',
|
||||
es: 'currículum',
|
||||
},
|
||||
icon: 'address-book-duotone',
|
||||
to: 'https://files.arthurdanjou.fr/s/resume',
|
||||
target: '_blank',
|
||||
},
|
||||
]
|
||||
import { navs, socials } from '~~/types'
|
||||
|
||||
const { locale, setLocale, locales, t } = useI18n()
|
||||
const currentLocale = computed(() => locales.value.filter(l => l.code === locale.value)[0])
|
||||
@@ -123,7 +27,6 @@ const openSelectMenu = ref(false)
|
||||
const openContactDrawer = ref(false)
|
||||
const router = useRouter()
|
||||
defineShortcuts({
|
||||
t: () => startViewTransition({ clientX: window.innerWidth, clientY: 0 }),
|
||||
l: () => changeLocale(),
|
||||
c: () => openContactDrawer.value = !openContactDrawer.value,
|
||||
backspace: () => router.back(),
|
||||
@@ -204,21 +107,7 @@ const socialsList = [
|
||||
</UDropdownMenu>
|
||||
<USeparator orientation="vertical" class="h-6" />
|
||||
<ClientOnly>
|
||||
<UTooltip
|
||||
:kbds="['T']"
|
||||
:text="t('theme')"
|
||||
class="cursor-pointer"
|
||||
:delay-duration="4"
|
||||
>
|
||||
<UButton
|
||||
:icon="nextTheme === 'dark' ? 'i-ph-moon-duotone' : 'i-ph-sun-duotone'"
|
||||
color="neutral"
|
||||
aria-label="switch theme"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
@click="startViewTransition"
|
||||
/>
|
||||
</UTooltip>
|
||||
<ThemeSwitcher />
|
||||
<UTooltip
|
||||
:kbds="['L']"
|
||||
:text="t('language')"
|
||||
@@ -278,7 +167,6 @@ const socialsList = [
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"theme": "switch theme",
|
||||
"language": "change language",
|
||||
"status": "status page",
|
||||
"contact": {
|
||||
@@ -287,7 +175,6 @@ const socialsList = [
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"theme": "changer de thème",
|
||||
"language": "changer de langue",
|
||||
"status": "page de statut",
|
||||
"contact": {
|
||||
@@ -296,7 +183,6 @@ const socialsList = [
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"theme": "cambiar tema",
|
||||
"language": "cambiar idioma",
|
||||
"status": "página de estado",
|
||||
"contact": {
|
||||
|
||||
@@ -3,6 +3,7 @@ const { visitors } = useVisitors()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<UBadge
|
||||
color="green"
|
||||
variant="outline"
|
||||
@@ -15,4 +16,5 @@ const { visitors } = useVisitors()
|
||||
<div class="w-3 h-3 bg-green-200/70 dark:bg-green-800/70 rounded-full border-2 border-green-400 dark:border-green-600" />
|
||||
</div>
|
||||
</UBadge>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
85
app/components/ThemeSwitcher.vue
Normal file
85
app/components/ThemeSwitcher.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
const colorMode = useColorMode()
|
||||
const nextTheme = computed(() => (colorMode.value === 'dark' ? 'light' : 'dark'))
|
||||
|
||||
function switchTheme() {
|
||||
colorMode.preference = nextTheme.value
|
||||
}
|
||||
|
||||
function startViewTransition(event: MouseEvent | { clientX: number, clientY: number }) {
|
||||
if (!document.startViewTransition) {
|
||||
switchTheme()
|
||||
return
|
||||
}
|
||||
|
||||
const x = event.clientX
|
||||
const y = event.clientY
|
||||
const endRadius = Math.hypot(
|
||||
Math.max(x, window.innerWidth - x),
|
||||
Math.max(y, window.innerHeight - y),
|
||||
)
|
||||
|
||||
const transition = document.startViewTransition(async () => {
|
||||
switchTheme()
|
||||
await nextTick()
|
||||
})
|
||||
|
||||
transition.ready.then(() => {
|
||||
const clipPath = [
|
||||
`circle(0px at ${x}px ${y}px)`,
|
||||
`circle(${endRadius}px at ${x}px ${y}px)`,
|
||||
]
|
||||
document.documentElement.animate(
|
||||
{
|
||||
clipPath: colorMode.value === 'dark'
|
||||
? [...clipPath].reverse()
|
||||
: clipPath,
|
||||
},
|
||||
{
|
||||
duration: 500,
|
||||
easing: 'ease-out',
|
||||
pseudoElement: colorMode.value === 'dark'
|
||||
? '::view-transition-old(root)'
|
||||
: '::view-transition-new(root)',
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
defineShortcuts({
|
||||
t: () => startViewTransition({ clientX: window.innerWidth, clientY: 0 }),
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTooltip
|
||||
:kbds="['T']"
|
||||
:text="t('theme')"
|
||||
class="cursor-pointer"
|
||||
:delay-duration="4"
|
||||
>
|
||||
<UButton
|
||||
:icon="nextTheme === 'dark' ? 'i-ph-moon-duotone' : 'i-ph-sun-duotone'"
|
||||
color="neutral"
|
||||
aria-label="switch theme"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
@click="startViewTransition"
|
||||
/>
|
||||
</UTooltip>
|
||||
</template>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"theme": "switch theme"
|
||||
},
|
||||
"fr": {
|
||||
"theme": "changer de thème"
|
||||
},
|
||||
"es": {
|
||||
"theme": "cambiar tema"
|
||||
},
|
||||
}
|
||||
</i18n>
|
||||
@@ -31,7 +31,7 @@ const { data: projects } = await useAsyncData('all-projects', () => {
|
||||
:to="project.path"
|
||||
>
|
||||
<li
|
||||
class="flex flex-col h-full group hover:bg-gray-100/60 duration-300 p-2 rounded-lg dark:hover:bg-neutral-800/30 transition-colors justify-center"
|
||||
class="flex flex-col h-full group hover:bg-gray-100/50 duration-300 p-2 rounded-lg dark:hover:bg-neutral-800/50 transition-colors justify-center"
|
||||
>
|
||||
<article class="space-y-2">
|
||||
<div
|
||||
@@ -53,7 +53,7 @@ const { data: projects } = await useAsyncData('all-projects', () => {
|
||||
v-if="project.favorite"
|
||||
name="i-ph-star-duotone"
|
||||
size="16"
|
||||
class="text-amber-500 hover:rotate-360 duration-300"
|
||||
class="text-amber-500 hover:rotate-360 duration-500"
|
||||
/>
|
||||
</UTooltip>
|
||||
</div>
|
||||
|
||||
@@ -47,7 +47,7 @@ const groupedWritings = computed(() => {
|
||||
:to="writing.path"
|
||||
>
|
||||
<li
|
||||
class="h-full group hover:bg-gray-100/60 duration-300 p-1 lg:p-2 rounded-lg dark:hover:bg-neutral-800/30 transition-colors"
|
||||
class="h-full group hover:bg-gray-100/30 duration-300 p-1 lg:p-2 rounded-lg dark:hover:bg-neutral-800/50 transition-colors"
|
||||
>
|
||||
<h1
|
||||
class="font-bold text-lg duration-300 text-neutral-600 dark:text-neutral-400 group-hover:text-neutral-900 dark:group-hover:text-white"
|
||||
|
||||
122
types/index.ts
122
types/index.ts
@@ -160,3 +160,125 @@ export const socials = [
|
||||
to: 'https://discordapp.com/users/179635349100691456',
|
||||
},
|
||||
]
|
||||
|
||||
interface Nav {
|
||||
label: {
|
||||
en: string
|
||||
fr: string
|
||||
es: string
|
||||
}
|
||||
to: string
|
||||
icon?: string
|
||||
target?: string
|
||||
}
|
||||
|
||||
interface NavColor {
|
||||
color: string
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
interface NavColorGroup {
|
||||
label: string
|
||||
colors: Array<NavColor>
|
||||
}
|
||||
|
||||
export const navs: Array<Nav> = [
|
||||
{
|
||||
label: {
|
||||
en: 'home',
|
||||
fr: 'accueil',
|
||||
es: 'inicio',
|
||||
},
|
||||
to: '/',
|
||||
icon: 'house-duotone',
|
||||
},
|
||||
{
|
||||
label: {
|
||||
en: 'uses',
|
||||
fr: 'usages',
|
||||
es: 'usos',
|
||||
},
|
||||
to: '/uses',
|
||||
icon: 'backpack-duotone',
|
||||
},
|
||||
{
|
||||
label: {
|
||||
en: 'writings',
|
||||
fr: 'écrits',
|
||||
es: 'escritos',
|
||||
},
|
||||
to: '/writings',
|
||||
icon: 'books-duotone',
|
||||
},
|
||||
{
|
||||
label: {
|
||||
en: 'projects',
|
||||
fr: 'projets',
|
||||
es: 'proyectos',
|
||||
},
|
||||
to: '/projects',
|
||||
icon: 'code-duotone',
|
||||
},
|
||||
{
|
||||
label: {
|
||||
en: 'resume',
|
||||
fr: 'cv',
|
||||
es: 'currículum',
|
||||
},
|
||||
icon: 'address-book-duotone',
|
||||
to: 'https://files.arthurdanjou.fr/s/resume',
|
||||
target: '_blank',
|
||||
},
|
||||
]
|
||||
|
||||
export const navColors: Array<NavColorGroup> = [
|
||||
{
|
||||
label: '/',
|
||||
colors: [{
|
||||
color: '#f59e0b',
|
||||
x: 20,
|
||||
y: 80,
|
||||
}, {
|
||||
color: '#ec4899',
|
||||
x: 80,
|
||||
y: 20,
|
||||
}],
|
||||
},
|
||||
{
|
||||
label: '/uses',
|
||||
colors: [{
|
||||
color: '#ec4899',
|
||||
x: 20,
|
||||
y: 80,
|
||||
}, {
|
||||
color: '#f59e0b',
|
||||
x: 80,
|
||||
y: 20,
|
||||
}],
|
||||
},
|
||||
{
|
||||
label: '/writings',
|
||||
colors: [{
|
||||
color: '#3b82f6',
|
||||
x: 20,
|
||||
y: 80,
|
||||
}, {
|
||||
color: '#ec4899',
|
||||
x: 80,
|
||||
y: 20,
|
||||
}],
|
||||
},
|
||||
{
|
||||
label: '/projects',
|
||||
colors: [{
|
||||
color: '#8b5cf6',
|
||||
x: 20,
|
||||
y: 80,
|
||||
}, {
|
||||
color: '#3b82f6',
|
||||
x: 80,
|
||||
y: 20,
|
||||
}],
|
||||
},
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user