mirror of
https://github.com/ArthurDanjou/website.git
synced 2026-01-14 12:14:42 +01:00
Working
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"extends": [
|
"extends": [
|
||||||
"@antfu",
|
"@antfu"
|
||||||
"@unocss"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
9
.vscode/settings.json
vendored
Normal file
9
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"files.associations": {
|
||||||
|
"*.css": "tailwindcss"
|
||||||
|
},
|
||||||
|
"editor.quickSuggestions": {
|
||||||
|
"strings": true
|
||||||
|
},
|
||||||
|
"tailwindCSS.experimental.configFile": "tailwind.config.ts"
|
||||||
|
}
|
||||||
42
README.md
42
README.md
@@ -1,42 +0,0 @@
|
|||||||
# Nuxt 3 Minimal Starter
|
|
||||||
|
|
||||||
Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
Make sure to install the dependencies:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# yarn
|
|
||||||
yarn install
|
|
||||||
|
|
||||||
# npm
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# pnpm
|
|
||||||
pnpm install
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development Server
|
|
||||||
|
|
||||||
Start the development server on `http://localhost:3000`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Production
|
|
||||||
|
|
||||||
Build the application for production:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
Locally preview production build:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run preview
|
|
||||||
```
|
|
||||||
|
|
||||||
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
|
||||||
@@ -10,32 +10,37 @@ export default defineNuxtConfig({
|
|||||||
],
|
],
|
||||||
|
|
||||||
modules: [
|
modules: [
|
||||||
|
'@nuxthq/ui',
|
||||||
'@pinia/nuxt',
|
'@pinia/nuxt',
|
||||||
'@pinia-plugin-persistedstate/nuxt',
|
'@pinia-plugin-persistedstate/nuxt',
|
||||||
'@unocss/nuxt',
|
|
||||||
'@nuxt/devtools',
|
'@nuxt/devtools',
|
||||||
'@nuxtjs/color-mode',
|
|
||||||
'@nuxtjs/fontaine',
|
|
||||||
'@vueuse/nuxt',
|
'@vueuse/nuxt',
|
||||||
'@nuxt/content',
|
'@nuxt/content',
|
||||||
'nuxt-icon',
|
|
||||||
],
|
],
|
||||||
|
|
||||||
colorMode: {
|
colorMode: {
|
||||||
preference: 'system',
|
preference: 'light',
|
||||||
fallback: 'light',
|
fallback: 'light',
|
||||||
classPrefix: '',
|
classPrefix: '',
|
||||||
classSuffix: '',
|
classSuffix: '',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
path: '~/components',
|
||||||
|
pathPrefix: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
tailwindcss: {
|
||||||
|
viewer: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
ui: {
|
||||||
|
icons: 'all',
|
||||||
|
},
|
||||||
|
|
||||||
devtools: {
|
devtools: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
vscode: {
|
|
||||||
enabled: true,
|
|
||||||
startOnBoot: true,
|
|
||||||
port: 3001,
|
|
||||||
reuseExistingServer: true,
|
|
||||||
mode: 'tunnel',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
55
package.json
55
package.json
@@ -11,39 +11,34 @@
|
|||||||
"lint:fix": "eslint . --fix"
|
"lint:fix": "eslint . --fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/content": "^2.6.0",
|
"@nuxt/content": "^2.7.2",
|
||||||
"@nuxt/image": "^0.7.1",
|
"@nuxt/image": "^0.7.1",
|
||||||
"@nuxtjs/color-mode": "^3.2.0",
|
"@pinia/nuxt": "0.4.11",
|
||||||
"@pinia/nuxt": "^0.4.9",
|
"@prisma/client": "^5.0.0",
|
||||||
"@prisma/client": "^4.13.0",
|
"@trpc/client": "10.35.0",
|
||||||
"@trpc/client": "^10.21.2",
|
"@trpc/server": "^10.35.0",
|
||||||
"@trpc/server": "^10.21.2",
|
"@vercel/analytics": "^1.0.1",
|
||||||
"@vercel/analytics": "^1.0.0",
|
"@vueuse/motion": "2.0.0",
|
||||||
"@vueuse/motion": "^2.0.0-beta.12",
|
"pinia": "^2.1.4",
|
||||||
"nuxt-icon": "^0.4.0",
|
"postcss-custom-properties": "13.3.0",
|
||||||
"pinia": "^2.0.35",
|
"sass": "^1.64.1",
|
||||||
"sass": "^1.62.1",
|
"superjson": "^1.13.1",
|
||||||
"superjson": "^1.12.3",
|
"tailwindcss": "^3.3.3",
|
||||||
"trpc-nuxt": "^0.9.0",
|
"trpc-nuxt": "^0.10.6",
|
||||||
"unocss": "^0.51.8",
|
|
||||||
"zod": "^3.21.4"
|
"zod": "^3.21.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@antfu/eslint-config": "0.26.3",
|
"@antfu/eslint-config": "0.39.8",
|
||||||
"@nuxt/devtools": "^0.4.2",
|
"@iconify/json": "2.2.94",
|
||||||
"@nuxtjs/fontaine": "^0.2.5",
|
"@nuxt/devtools": "0.7.1",
|
||||||
"@pinia-plugin-persistedstate/nuxt": "^1.1.1",
|
"@nuxthq/ui": "^2.6.0",
|
||||||
"@types/node": "^18",
|
"@pinia-plugin-persistedstate/nuxt": "1.1.1",
|
||||||
"@unocss/eslint-config": "^0.51.8",
|
"@types/node": "20.4.4",
|
||||||
"@unocss/nuxt": "^0.51.8",
|
"@vueuse/core": "^10.2.1",
|
||||||
"@unocss/transformer-directives": "^0.51.12",
|
"@vueuse/nuxt": "^10.2.1",
|
||||||
"@unocss/transformer-variant-group": "^0.51.8",
|
"eslint": "^8.45.0",
|
||||||
"@vueuse/core": "^10.1.0",
|
"nuxt": "^3.6.5",
|
||||||
"@vueuse/nuxt": "^10.1.0",
|
"prisma": "^5.0.0",
|
||||||
"eslint": "^8.39.0",
|
"typescript": "5.1.6"
|
||||||
"nuxt": "^3.4.2",
|
|
||||||
"prisma": "^4.13.0",
|
|
||||||
"typescript": "^5.0.4",
|
|
||||||
"unocss-preset-scrollbar": "^0.2.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
22
src/app.config.ts
Normal file
22
src/app.config.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
export default defineAppConfig({
|
||||||
|
ui: {
|
||||||
|
gray: 'neutral',
|
||||||
|
notifications: {
|
||||||
|
position: 'bottom-0 right-0',
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
base: 'mx-auto',
|
||||||
|
padding: 'px-4 sm:px-6 lg:px-8',
|
||||||
|
constrained: 'max-w-7xl',
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
background: 'bg-white dark:bg-zinc-900',
|
||||||
|
},
|
||||||
|
dropdown: {
|
||||||
|
background: 'bg-white dark:bg-zinc-800',
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
base: 'duration-300 focus:outline-none focus-visible:outline-0 disabled:cursor-not-allowed disabled:opacity-75 flex-shrink-0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
49
src/app/router.options.ts
Normal file
49
src/app/router.options.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import type { RouterConfig } from '@nuxt/schema'
|
||||||
|
|
||||||
|
function findHashPosition(hash: any): { el: any; behavior: ScrollBehavior; top: number } | undefined {
|
||||||
|
const el = document.querySelector(hash)
|
||||||
|
// vue-router does not incorporate scroll-margin-top on its own.
|
||||||
|
if (el) {
|
||||||
|
const top = parseFloat(getComputedStyle(el).scrollMarginTop)
|
||||||
|
|
||||||
|
return {
|
||||||
|
el: hash,
|
||||||
|
behavior: 'smooth',
|
||||||
|
top,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://router.vuejs.org/api/#routeroptions
|
||||||
|
export default <RouterConfig>{
|
||||||
|
scrollBehavior(to, from, savedPosition) {
|
||||||
|
const nuxtApp = useNuxtApp()
|
||||||
|
|
||||||
|
// If history back
|
||||||
|
if (savedPosition) {
|
||||||
|
// Handle Suspense resolution
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
nuxtApp.hooks.hookOnce('page:finish', () => {
|
||||||
|
setTimeout(() => resolve(savedPosition), 50)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll to heading on click
|
||||||
|
if (to.hash) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (to.path === from.path) {
|
||||||
|
setTimeout(() => resolve(findHashPosition(to.hash)), 50)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
nuxtApp.hooks.hookOnce('page:finish', () => {
|
||||||
|
setTimeout(() => resolve(findHashPosition(to.hash)), 50)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll to top of window
|
||||||
|
return { top: 0 }
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
body {
|
body {
|
||||||
|
|
||||||
font-family: 'DM Sans', sans-serif;
|
font-family: 'DM Sans', sans-serif;
|
||||||
@apply bg-white dark:bg-dark-900 dark:text-white duration-200
|
@apply flex h-full flex-col bg-zinc-50 dark:bg-black;
|
||||||
}
|
}
|
||||||
|
|||||||
13
src/assets/css/tailwind.css
Normal file
13
src/assets/css/tailwind.css
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.w-container {
|
||||||
|
@apply mx-auto max-w-7xl lg:px-24 sm:px-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-subtitle {
|
||||||
|
@apply text-zinc-600 dark:text-zinc-400 text-base
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
const { $trpc } = useNuxtApp()
|
|
||||||
const announcement = await $trpc.announcement.announcement.query()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-if="announcement" flex justify-center>
|
|
||||||
<div dark:hover:bg="dark-800/50" hover:bg="gray-100/50" shadow-announcement-light dark:shadow-announcement-dark rounded-md p-4 duration-300 dark:bg-dark-900>
|
|
||||||
<p v-html="announcement.content" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
130
src/components/CommandPalette.vue
Normal file
130
src/components/CommandPalette.vue
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const isOpen = ref(false)
|
||||||
|
const commandPaletteRef = ref()
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const color = useColorMode()
|
||||||
|
|
||||||
|
const quickLinks = [
|
||||||
|
{ id: 'twitter', label: 'Twitter', icon: 'i-ph-twitter-logo-bold' },
|
||||||
|
{ id: 'github', label: 'GitHub', icon: 'i-ph-github-logo-bold' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const navigations = [
|
||||||
|
{ id: 'home', label: 'Home', icon: 'i-ph-house-bold', to: '/', shortcuts: ['H'] },
|
||||||
|
{ id: 'about', label: 'About', icon: 'i-ph-user-bold', to: '/about', shortcuts: ['A'] },
|
||||||
|
{ id: 'blog', label: 'Blog', icon: 'i-ph-book-bold', to: '/blog', shortcuts: ['B'] },
|
||||||
|
{ id: 'work', label: 'Work', icon: 'i-ph-wrench-bold', to: '/work', shortcuts: ['W'] },
|
||||||
|
]
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const isDark = computed(() => color.preference === 'dark')
|
||||||
|
const controls = [
|
||||||
|
{
|
||||||
|
id: 'color',
|
||||||
|
label: 'Toggle Color Mode',
|
||||||
|
icon: isDark ? 'i-ph-moon-bold' : 'i-ph-sun-bold',
|
||||||
|
click: () => {
|
||||||
|
color.preference = color.value === 'dark' ? 'light' : 'dark'
|
||||||
|
toast.add({
|
||||||
|
color: 'primary',
|
||||||
|
title: 'Color mode switched!',
|
||||||
|
icon: isDark.value ? 'i-ph-moon-bold' : 'i-ph-sun-bold',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
shortcuts: ['C'],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const groups = [{
|
||||||
|
key: 'navigation',
|
||||||
|
label: 'Navigation',
|
||||||
|
inactive: 'Navigation',
|
||||||
|
commands: navigations,
|
||||||
|
}, {
|
||||||
|
key: 'quickLinks',
|
||||||
|
label: 'Quick Links',
|
||||||
|
inactive: 'Quick Links',
|
||||||
|
commands: quickLinks,
|
||||||
|
}, {
|
||||||
|
key: 'controls',
|
||||||
|
label: 'Controls',
|
||||||
|
inactive: 'Controls',
|
||||||
|
commands: controls,
|
||||||
|
}]
|
||||||
|
|
||||||
|
const { usingInput } = useShortcuts()
|
||||||
|
const canToggleModal = computed(() => isOpen.value || !usingInput.value)
|
||||||
|
|
||||||
|
defineShortcuts({
|
||||||
|
meta_k: {
|
||||||
|
usingInput: true,
|
||||||
|
whenever: [canToggleModal],
|
||||||
|
handler: () => {
|
||||||
|
isOpen.value = !isOpen.value
|
||||||
|
},
|
||||||
|
},
|
||||||
|
escape: {
|
||||||
|
usingInput: true,
|
||||||
|
whenever: [isOpen],
|
||||||
|
handler: () => { isOpen.value = false },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
function onSelect(option: any) {
|
||||||
|
if (option.click) {
|
||||||
|
option.click()
|
||||||
|
isOpen.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (option.to) { router.push(option.to) }
|
||||||
|
|
||||||
|
else if (option.href) { window.open(option.href, '_blank') }
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyStroke(true, (event: KeyboardEvent) => {
|
||||||
|
if (!isOpen.value && !usingInput.value) {
|
||||||
|
switch (event.key) {
|
||||||
|
case 'A':
|
||||||
|
case 'a':
|
||||||
|
router.push('/about')
|
||||||
|
break
|
||||||
|
case 'H':
|
||||||
|
case 'h':
|
||||||
|
router.push('/')
|
||||||
|
break
|
||||||
|
case 'W':
|
||||||
|
case 'w':
|
||||||
|
router.push('/work')
|
||||||
|
break
|
||||||
|
case 'B':
|
||||||
|
case 'b':
|
||||||
|
router.push('/blog')
|
||||||
|
break
|
||||||
|
case 'C':
|
||||||
|
case 'c':
|
||||||
|
color.preference = color.value === 'dark' ? 'light' : 'dark'
|
||||||
|
toast.add({
|
||||||
|
color: 'primary',
|
||||||
|
title: 'Color mode switched!',
|
||||||
|
icon: isDark.value ? 'i-ph-moon-bold' : 'i-ph-sun-bold',
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UModal v-model="isOpen">
|
||||||
|
<UCommandPalette
|
||||||
|
ref="commandPaletteRef"
|
||||||
|
:groups="groups"
|
||||||
|
icon=""
|
||||||
|
:autoselect="false"
|
||||||
|
placeholder="Search for apps and commands"
|
||||||
|
@update:model-value="onSelect"
|
||||||
|
/>
|
||||||
|
</UModal>
|
||||||
|
</template>
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<template>
|
|
||||||
<footer>
|
|
||||||
Footer
|
|
||||||
</footer>
|
|
||||||
</template>
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
const { getThemeTextColor } = useTheme()
|
|
||||||
const { y } = useWindowScroll()
|
|
||||||
|
|
||||||
const color = useColorMode()
|
|
||||||
const toggleColor = () => {
|
|
||||||
color.preference = color.value === 'dark' ? 'light' : 'dark'
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<header
|
|
||||||
sticky top-0 h-16 duration-300
|
|
||||||
:class="{ 'backdrop-blur-10px backdrop-saturate-180 shadow-header-active-light dark:shadow-header-active-dark': y > 8 }"
|
|
||||||
>
|
|
||||||
<div h-full flex items-center justify-between px-32>
|
|
||||||
<div>
|
|
||||||
Logo
|
|
||||||
</div>
|
|
||||||
<div flex items-center space-x-4>
|
|
||||||
<nav flex space-x-4>
|
|
||||||
<NuxtLink to="/about">
|
|
||||||
About
|
|
||||||
</NuxtLink>
|
|
||||||
<NuxtLink to="/writings">
|
|
||||||
Blog
|
|
||||||
</NuxtLink>
|
|
||||||
<NuxtLink to="/work">
|
|
||||||
Work
|
|
||||||
</NuxtLink>
|
|
||||||
<NuxtLink to="/contact">
|
|
||||||
Contact
|
|
||||||
</NuxtLink>
|
|
||||||
<NuxtLink to="/test">
|
|
||||||
TEST
|
|
||||||
</NuxtLink>
|
|
||||||
</nav>
|
|
||||||
<div h-24px w-1px bg-gray-300 dark:bg-dark-300 />
|
|
||||||
<button
|
|
||||||
role="switch" type="button"
|
|
||||||
relative block h-22px w-10 shrink-0
|
|
||||||
border-1 border-gray-400 rounded-11px border-solid
|
|
||||||
transition-colors-250
|
|
||||||
dark:border-dark-300 hover:border-gray-600 dark:hover:border-dark-100
|
|
||||||
@click.prevent="toggleColor()"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
rounded="1/2" absolute left-1px top-1px h-18px w-18px transition-transform-250
|
|
||||||
:style="{ transform: `translateX(${color.preference === 'light' ? 0 : 18}px)` }"
|
|
||||||
>
|
|
||||||
<span relative block h-18px w-18px overflow-hidden rounded="1/2" bg-gray-100 dark:bg-dark-400>
|
|
||||||
<Icon
|
|
||||||
name="material-symbols:dark-mode-outline" size="12"
|
|
||||||
:class="color.preference === 'light' ? 'opacity-0' : 'opacity-100'"
|
|
||||||
absolute left-3px top-3px text-black duration-200 dark:text-white
|
|
||||||
/>
|
|
||||||
<Icon
|
|
||||||
name="material-symbols:light-mode-outline" size="12"
|
|
||||||
:class="color.preference === 'light' ? 'opacity-100' : 'opacity-0'"
|
|
||||||
absolute left-3px top-3px text-black duration-200 dark:text-white
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<div h-24px w-1px bg-gray-300 dark:bg-dark-300 />
|
|
||||||
<div flex space-x-4>
|
|
||||||
<NuxtLink
|
|
||||||
href="https://github.com/ArthurDanjou" target="_blank" text-gray-700 duration-300 dark:text-gray-400
|
|
||||||
:class="`hover:${getThemeTextColor}`"
|
|
||||||
>
|
|
||||||
<Icon name="mdi:github" size="24" />
|
|
||||||
</NuxtLink>
|
|
||||||
<NuxtLink
|
|
||||||
href="https://twitter.com/ArthurDanj" target="_blank" text-gray-700 duration-300 dark:text-gray-400
|
|
||||||
:class="`hover:${getThemeTextColor}`"
|
|
||||||
>
|
|
||||||
<Icon name="mdi:twitter" size="24" />
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
a.router-link-exact-active {
|
|
||||||
color: v-bind(getThemeTextColor)
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
53
src/components/MainBanner.vue
Normal file
53
src/components/MainBanner.vue
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<script setup>
|
||||||
|
const socials = [
|
||||||
|
{
|
||||||
|
name: 'mail',
|
||||||
|
icon: 'i-material-symbols-alternate-email',
|
||||||
|
link: 'mailto:arthurdanjou@outlook.fr',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'twitter',
|
||||||
|
icon: 'i-ph-twitter-logo-bold',
|
||||||
|
link: 'https://twitter.com/ArthurDanj',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'github',
|
||||||
|
icon: 'i-ph-github-logo-bold',
|
||||||
|
link: 'https://twitter.com/ArthurDanj',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'linkedin',
|
||||||
|
icon: 'i-ph-linkedin-logo-bold',
|
||||||
|
link: 'https://www.linkedin.com/in/arthurdanjou/',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-container lg:my-32">
|
||||||
|
<div class="max-w-2xl space-y-8">
|
||||||
|
<h1 class="text-4xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-5xl !leading-tight">
|
||||||
|
Software engineer and mathematics lover
|
||||||
|
</h1>
|
||||||
|
<p class="leading-relaxed text-subtitle">
|
||||||
|
I'm Arthur, a software engineer passionate about artificial intelligence and the cloud but also a mathematics student living in France. I am currently studying mathematics at the Faculty of Sciences of Paris-Saclay.
|
||||||
|
</p>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<UButton
|
||||||
|
v-for="social in socials"
|
||||||
|
:key="social.name"
|
||||||
|
:icon="social.icon"
|
||||||
|
size="md"
|
||||||
|
:link="social.link"
|
||||||
|
variant="ghost"
|
||||||
|
target="_blank"
|
||||||
|
:ui="{ rounded: 'rounded-full' }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
defineProps<{
|
|
||||||
active: boolean
|
|
||||||
}>()
|
|
||||||
|
|
||||||
defineEmits<{
|
|
||||||
(e: 'click'): void
|
|
||||||
}>()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="menu-burger"
|
|
||||||
:class="{ active }"
|
|
||||||
@click="$emit('click')"
|
|
||||||
>
|
|
||||||
<span class="container">
|
|
||||||
<span class="top" />
|
|
||||||
<span class="middle" />
|
|
||||||
<span class="bottom" />
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.menu-burger {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 48px;
|
|
||||||
height: var(--vp-nav-height);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.menu-burger {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
position: relative;
|
|
||||||
width: 16px;
|
|
||||||
height: 14px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-burger:hover .top { top: 0; left: 0; transform: translateX(4px); }
|
|
||||||
.menu-burger:hover .middle { top: 6px; left: 0; transform: translateX(0); }
|
|
||||||
.menu-burger:hover .bottom { top: 12px; left: 0; transform: translateX(8px); }
|
|
||||||
|
|
||||||
.menu-burger.active .top { top: 6px; transform: translateX(0) rotate(225deg); }
|
|
||||||
.menu-burger.active .middle { top: 6px; transform: translateX(16px); }
|
|
||||||
.menu-burger.active .bottom { top: 6px; transform: translateX(0) rotate(135deg); }
|
|
||||||
|
|
||||||
.menu-burger.active:hover .top,
|
|
||||||
.menu-burger.active:hover .middle,
|
|
||||||
.menu-burger.active:hover .bottom {
|
|
||||||
background-color: var(--vp-c-text-2);
|
|
||||||
transition: top .25s, background-color .25s, transform .25s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top,
|
|
||||||
.middle,
|
|
||||||
.bottom {
|
|
||||||
position: absolute;
|
|
||||||
width: 16px;
|
|
||||||
height: 2px;
|
|
||||||
background-color: var(--vp-c-text-1);
|
|
||||||
transition: top .25s, background-color .5s, transform .25s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top { top: 0; left: 0; transform: translateX(0); }
|
|
||||||
.middle { top: 6px; left: 0; transform: translateX(8px); }
|
|
||||||
.bottom { top: 12px; left: 0; transform: translateX(4px); }
|
|
||||||
</style>
|
|
||||||
24
src/components/header/ColorModeButton.vue
Normal file
24
src/components/header/ColorModeButton.vue
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const colorMode = useColorMode()
|
||||||
|
|
||||||
|
const isDark = computed({
|
||||||
|
get() {
|
||||||
|
return colorMode.value === 'dark'
|
||||||
|
},
|
||||||
|
set() {
|
||||||
|
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UButton
|
||||||
|
:icon="isDark ? 'i-heroicons-moon-20-solid' : 'i-heroicons-sun-20-solid'"
|
||||||
|
variant="solid"
|
||||||
|
aria-label="Theme"
|
||||||
|
color="primary"
|
||||||
|
square
|
||||||
|
size="sm"
|
||||||
|
@click="isDark = !isDark"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
46
src/components/header/ColorPicker.vue
Normal file
46
src/components/header/ColorPicker.vue
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useColorStore } from '~/store/color'
|
||||||
|
import { ColorsTheme } from '~~/types'
|
||||||
|
|
||||||
|
const colors = Object.values(ColorsTheme)
|
||||||
|
|
||||||
|
const { setColor, getColor } = useColorStore()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UPopover
|
||||||
|
:open-delay="60"
|
||||||
|
:close-delay="10"
|
||||||
|
:ui="{
|
||||||
|
background: 'bg-white dark:bg-stone-900',
|
||||||
|
ring: 'ring-1 ring-gray-200 dark:ring-stone-800',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<UButton trailing-icon="i-heroicons-swatch-20-solid" variant="ghost" color="primary" size="sm" />
|
||||||
|
|
||||||
|
<template #panel>
|
||||||
|
<div class="p-2">
|
||||||
|
<div class="grid grid-cols-5 gap-px">
|
||||||
|
<UTooltip v-for="color in colors" :key="color" :text="color" class="capitalize" :open-delay="500">
|
||||||
|
<UButton
|
||||||
|
color="gray"
|
||||||
|
square
|
||||||
|
:ui="{
|
||||||
|
color: {
|
||||||
|
gray: {
|
||||||
|
solid: 'bg-gray-100 dark:bg-stone-800',
|
||||||
|
ghost: 'hover:bg-gray-50 dark:hover:bg-stone-800/50',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
:variant="color === getColor ? 'solid' : 'ghost'"
|
||||||
|
@click.stop.prevent="setColor(color)"
|
||||||
|
>
|
||||||
|
<span class="inline-block w-3 h-3 rounded-full" :class="`bg-${color}-500`" />
|
||||||
|
</UButton>
|
||||||
|
</UTooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UPopover>
|
||||||
|
</template>
|
||||||
15
src/components/header/Header.vue
Normal file
15
src/components/header/Header.vue
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w-container flex justify-between py-6 sticky top-0 left-0 bg-white dark:bg-zinc-900 border-b border-zinc-100 dark:border-zinc-300/10">
|
||||||
|
<Logo />
|
||||||
|
<NavBar />
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<ClientOnly>
|
||||||
|
<div class="flex items-center rounded-md p-1 gap-1 relative bg-black/5 text-sm font-medium text-zinc-700 dark:bg-zinc-800/90 dark:text-zinc-300">
|
||||||
|
<ColorPicker />
|
||||||
|
<ColorModeButton />
|
||||||
|
</div>
|
||||||
|
<MobileNavBar />
|
||||||
|
</ClientOnly>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
13
src/components/header/Logo.vue
Normal file
13
src/components/header/Logo.vue
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
LOGO
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
||||||
37
src/components/header/MobileNavBar.vue
Normal file
37
src/components/header/MobileNavBar.vue
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
const isOpen = ref(false)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="md:hidden">
|
||||||
|
<div class="p-1 rounded-md bg-black/5 text-sm font-medium text-zinc-700 dark:bg-zinc-800/90 dark:text-zinc-300">
|
||||||
|
<UButton
|
||||||
|
variant="ghost"
|
||||||
|
color="primary"
|
||||||
|
size="sm"
|
||||||
|
icon="i-ph-list-bold"
|
||||||
|
@click="isOpen = true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<USlideover v-model="isOpen">
|
||||||
|
<UCard class="flex flex-col flex-1" :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
||||||
|
<template #header>
|
||||||
|
<div>
|
||||||
|
Header
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
Content
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
Footer
|
||||||
|
</template>
|
||||||
|
</UCard>
|
||||||
|
</USlideover>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
||||||
57
src/components/header/NavBar.vue
Normal file
57
src/components/header/NavBar.vue
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const appConfig = useAppConfig()
|
||||||
|
|
||||||
|
function getTextColor() {
|
||||||
|
return `!text-${appConfig.ui.primary}-500`
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
[{
|
||||||
|
label: 'Talents',
|
||||||
|
to: '/talents',
|
||||||
|
icon: 'i-ph-users-bold',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'bookmarks',
|
||||||
|
to: '/bookmarks',
|
||||||
|
icon: 'i-ph-bookmark-simple-bold',
|
||||||
|
}],
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<header class="hidden md:block pointer-events-auto">
|
||||||
|
<div class="flex items-center h-10 rounded-md p-1 gap-1 relative bg-black/5 text-sm font-medium text-zinc-700 dark:bg-zinc-800/90 dark:text-zinc-300">
|
||||||
|
<UButton to="/" size="sm" variant="ghost" color="white">
|
||||||
|
Home
|
||||||
|
</UButton>
|
||||||
|
<UButton to="/about" size="sm" variant="ghost" color="white">
|
||||||
|
About
|
||||||
|
</UButton>
|
||||||
|
|
||||||
|
<UButton to="/blog" size="sm" variant="ghost" color="white">
|
||||||
|
Articles
|
||||||
|
</UButton>
|
||||||
|
<UButton to="/work" size="sm" variant="ghost" color="white">
|
||||||
|
Projects
|
||||||
|
</UButton>
|
||||||
|
<UButton to="/uses" size="sm" variant="ghost" color="white">
|
||||||
|
Uses
|
||||||
|
</UButton>
|
||||||
|
<UDropdown mode="hover" :items="items" :popper="{ placement: 'bottom' }">
|
||||||
|
<UButton size="sm" variant="ghost" color="white">
|
||||||
|
Other
|
||||||
|
</UButton>
|
||||||
|
</UDropdown>
|
||||||
|
<UButton to="/contact" size="sm" variant="ghost" color="white">
|
||||||
|
Contact
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.router-link-exact-active {
|
||||||
|
@apply bg-white/60 dark:bg-black
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
import { useThemeStore } from '~/store/theme'
|
|
||||||
import { ColorsTheme } from '~~/types'
|
|
||||||
|
|
||||||
export const useTheme = () => {
|
|
||||||
const { getColor } = useThemeStore()
|
|
||||||
|
|
||||||
const getThemeTextColor = computed(() => {
|
|
||||||
switch (getColor.value) {
|
|
||||||
case ColorsTheme.BLUE:
|
|
||||||
return 'text-blue-500'
|
|
||||||
case ColorsTheme.ROSE:
|
|
||||||
return 'text-rose-500'
|
|
||||||
case ColorsTheme.ORANGE:
|
|
||||||
return 'text-orange-500'
|
|
||||||
case ColorsTheme.CYAN:
|
|
||||||
return 'text-cyan-500'
|
|
||||||
case ColorsTheme.GREEN:
|
|
||||||
return 'text-green-500'
|
|
||||||
case ColorsTheme.PURPLE:
|
|
||||||
return 'text-purple-500'
|
|
||||||
case ColorsTheme.RED:
|
|
||||||
return 'text-red-500'
|
|
||||||
case ColorsTheme.YELLOW:
|
|
||||||
return 'text-yellow-500'
|
|
||||||
case ColorsTheme.FUCHSIA:
|
|
||||||
return 'text-fuchsia-500'
|
|
||||||
case ColorsTheme.PINK:
|
|
||||||
return 'text-pink-500'
|
|
||||||
case ColorsTheme.VIOLET:
|
|
||||||
return 'text-violet-500'
|
|
||||||
case ColorsTheme.BLACK:
|
|
||||||
return 'text-black dark:text-white'
|
|
||||||
case ColorsTheme.WHITE:
|
|
||||||
return 'text-black dark:text-white'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const getThemeBackgroundColor = computed(() => {
|
|
||||||
switch (getColor.value) {
|
|
||||||
case ColorsTheme.BLUE:
|
|
||||||
return 'bg-blue-500'
|
|
||||||
case ColorsTheme.ROSE:
|
|
||||||
return 'bg-rose-500'
|
|
||||||
case ColorsTheme.ORANGE:
|
|
||||||
return 'bg-orange-500'
|
|
||||||
case ColorsTheme.CYAN:
|
|
||||||
return 'bg-cyan-500'
|
|
||||||
case ColorsTheme.GREEN:
|
|
||||||
return 'bg-green-500'
|
|
||||||
case ColorsTheme.PURPLE:
|
|
||||||
return 'bg-purple-500'
|
|
||||||
case ColorsTheme.RED:
|
|
||||||
return 'bg-red-500'
|
|
||||||
case ColorsTheme.YELLOW:
|
|
||||||
return 'bg-yellow-500'
|
|
||||||
case ColorsTheme.FUCHSIA:
|
|
||||||
return 'bg-fuchsia-500'
|
|
||||||
case ColorsTheme.PINK:
|
|
||||||
return 'bg-pink-500'
|
|
||||||
case ColorsTheme.VIOLET:
|
|
||||||
return 'bg-violet-500'
|
|
||||||
case ColorsTheme.BLACK:
|
|
||||||
return 'bg-black dark:bg-white dark:text-black text-white'
|
|
||||||
case ColorsTheme.WHITE:
|
|
||||||
return 'bg-black dark:bg-white dark:text-black text-white'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
getThemeBackgroundColor,
|
|
||||||
getThemeTextColor,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<Header />
|
<CommandPalette />
|
||||||
<slot />
|
<div class="fixed inset-0 flex justify-center sm:px-8">
|
||||||
<Footer />
|
<div class="flex w-full max-w-7xl lg:px-8">
|
||||||
</template>
|
<div class="w-full bg-white ring-1 ring-zinc-100 dark:bg-zinc-900 dark:ring-zinc-300/20" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<UContainer class="relative z-50">
|
||||||
|
<Header />
|
||||||
|
<div class="min-h-screen">
|
||||||
|
<NuxtPage />
|
||||||
|
</div>
|
||||||
|
</UContainer>
|
||||||
|
|
||||||
<style>
|
<UNotifications />
|
||||||
@import '@unocss/reset/tailwind.css';
|
</template>
|
||||||
</style>
|
|
||||||
|
|||||||
13
src/pages/about.vue
Normal file
13
src/pages/about.vue
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<script setup>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
ABOUT
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useThemeStore } from '~/store/theme'
|
const days = ref(0)
|
||||||
|
useHead({
|
||||||
const { swapColor } = useThemeStore()
|
title: 'Arthur Danjou • Software Engineer and Maths Lover',
|
||||||
onMounted(() => swapColor())
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section>
|
<section>
|
||||||
Hey
|
<MainBanner />
|
||||||
<Announcement />
|
<NewsletterCard />
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { useThemeStore } from '~/store/theme'
|
|
||||||
|
|
||||||
const { getColor, getTheme, swapColor, nextTheme } = useThemeStore()
|
|
||||||
const { getThemeTextColor, getThemeBackgroundColor } = useTheme()
|
|
||||||
|
|
||||||
onMounted(() => swapColor())
|
|
||||||
|
|
||||||
const color = useColorMode()
|
|
||||||
const { query } = useRoute()
|
|
||||||
|
|
||||||
const { $trpc } = useNuxtApp()
|
|
||||||
const user = await $trpc.hello.query({ name: query.name?.toString() })
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<section>
|
|
||||||
<h1 mt-16 :class="`${getThemeTextColor}`" duration="1500">
|
|
||||||
Main page
|
|
||||||
</h1>
|
|
||||||
<h1 :class="`${getThemeBackgroundColor}`" duration="1500">
|
|
||||||
Main Page
|
|
||||||
</h1>
|
|
||||||
<div>
|
|
||||||
Current color : {{ getColor }}
|
|
||||||
</div>
|
|
||||||
<div my-12>
|
|
||||||
<div>Theme symbol : {{ getTheme.symbol }}</div>
|
|
||||||
<div>Theme Name : {{ getTheme.name }}</div>
|
|
||||||
<div>Theme colors : {{ getTheme.colors.map((color) => color.charAt(0).toUpperCase() + color.slice(1)).join(', ') }}</div>
|
|
||||||
</div>
|
|
||||||
<div @click="nextTheme()">
|
|
||||||
setNextTheme()
|
|
||||||
</div>
|
|
||||||
<div @click="color.preference = color.value === 'dark' ? 'light' : 'dark'">
|
|
||||||
toggleDarkMode()
|
|
||||||
</div>
|
|
||||||
<div my-48 h-32>
|
|
||||||
{{ user.greeting }}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { inject } from '@vercel/analytics'
|
import { inject } from '@vercel/analytics'
|
||||||
|
|
||||||
export default () => {
|
export default defineNuxtPlugin(() => {
|
||||||
inject()
|
inject()
|
||||||
}
|
})
|
||||||
|
|||||||
28
src/store/color.ts
Normal file
28
src/store/color.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ColorsTheme } from '~~/types'
|
||||||
|
|
||||||
|
export const useColorStore = defineStore(
|
||||||
|
'color',
|
||||||
|
() => {
|
||||||
|
const colorCookie = useCookie('color', { path: '/', default: () => ColorsTheme.RED })
|
||||||
|
|
||||||
|
const appConfig = useAppConfig()
|
||||||
|
watch(colorCookie, (newColor) => {
|
||||||
|
appConfig.ui.primary = newColor
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
const setColor = (color: string) => {
|
||||||
|
colorCookie.value = color as ColorsTheme
|
||||||
|
}
|
||||||
|
|
||||||
|
const getColor = computed(() => colorCookie)
|
||||||
|
|
||||||
|
return {
|
||||||
|
getColor,
|
||||||
|
setColor,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
persist: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import { defineStore } from 'pinia'
|
|
||||||
import type { ColorsTheme, Theme } from '~~/types'
|
|
||||||
import { THEMES, Themes } from '~~/types'
|
|
||||||
|
|
||||||
export const useThemeStore = defineStore(
|
|
||||||
'theme',
|
|
||||||
() => {
|
|
||||||
const currentTheme = ref<Theme>(Themes[THEMES.RainbowTheme])
|
|
||||||
const currentColor = ref<ColorsTheme>(currentTheme.value.colors[0])
|
|
||||||
const intervalId = ref<NodeJS.Timeout | null>(null)
|
|
||||||
|
|
||||||
const isAvailable = (next: Theme): boolean => {
|
|
||||||
if (!next.availability)
|
|
||||||
return true
|
|
||||||
|
|
||||||
const today = new Date()
|
|
||||||
const [startDay, startMonth] = next.availability.start.split('/')
|
|
||||||
const [endDay, endMonth] = next.availability.end.split('/')
|
|
||||||
const start = new Date(today.getFullYear(), Number(startMonth) - 1, Number(startDay))
|
|
||||||
const end = new Date(today.getFullYear(), Number(endMonth) - 1, Number(endDay))
|
|
||||||
|
|
||||||
return today >= start && today <= end
|
|
||||||
}
|
|
||||||
|
|
||||||
const swapColor = () => {
|
|
||||||
if (intervalId.value !== null)
|
|
||||||
clearInterval(intervalId.value)
|
|
||||||
|
|
||||||
intervalId.value = setInterval(() => {
|
|
||||||
const colors = currentTheme.value.colors
|
|
||||||
const currentIndex = colors.indexOf(currentColor.value)
|
|
||||||
const nextIndex = (currentIndex + 1) % colors.length
|
|
||||||
currentColor.value = colors[nextIndex]
|
|
||||||
}, 5000)
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextTheme = () => {
|
|
||||||
const themes = Object.values(Themes)
|
|
||||||
const currentIndex = themes.findIndex(theme => theme.name === currentTheme.value.name)
|
|
||||||
let nextIndex = (currentIndex + 1) % themes.length
|
|
||||||
|
|
||||||
while (!isAvailable(themes[nextIndex])) {
|
|
||||||
nextIndex = (nextIndex + 1) % themes.length
|
|
||||||
if (nextIndex === currentIndex)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
currentTheme.value = themes[nextIndex]
|
|
||||||
currentColor.value = currentTheme.value.colors[0]
|
|
||||||
swapColor()
|
|
||||||
}
|
|
||||||
|
|
||||||
const getTheme = computed(() => currentTheme)
|
|
||||||
const getColor = computed(() => currentColor)
|
|
||||||
|
|
||||||
return {
|
|
||||||
getTheme,
|
|
||||||
getColor,
|
|
||||||
nextTheme,
|
|
||||||
swapColor,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
persist: true,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
23
tailwind.config.ts
Normal file
23
tailwind.config.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type { Config } from 'tailwindcss'
|
||||||
|
import { ColorsTheme } from './types'
|
||||||
|
|
||||||
|
export default <Partial<Config>>{
|
||||||
|
safelist: [
|
||||||
|
// Theme text colors
|
||||||
|
...Object.values(ColorsTheme).map(color => `text-${color}-500`),
|
||||||
|
...Object.values(ColorsTheme).map(color => `hover:text-${color}-500`),
|
||||||
|
...'bg-black dark:bg-white dark:text-black text-white'.split(' '),
|
||||||
|
|
||||||
|
// Theme background colors
|
||||||
|
...Object.values(ColorsTheme).map(color => `bg-${color}-500`),
|
||||||
|
...Object.values(ColorsTheme).map(color => `hover:bg-${color}-500`),
|
||||||
|
...'text-black dark:text-white'.split(' '),
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
boxShadow: {
|
||||||
|
card: '0 0 10px 1px rgba(0,0,0,.1)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
126
types.ts
126
types.ts
@@ -1,117 +1,19 @@
|
|||||||
// Define a theme
|
|
||||||
export enum ColorsTheme {
|
export enum ColorsTheme {
|
||||||
ORANGE = 'orange',
|
|
||||||
YELLOW = 'yellow',
|
|
||||||
GREEN = 'green',
|
|
||||||
BLUE = 'blue',
|
|
||||||
PURPLE = 'purple',
|
|
||||||
ROSE = 'rose',
|
|
||||||
RED = 'red',
|
RED = 'red',
|
||||||
|
ORANGE = 'orange',
|
||||||
|
AMBER = 'amber',
|
||||||
|
YELLOW = 'yellow',
|
||||||
|
LIME = 'lime',
|
||||||
|
GREEN = 'green',
|
||||||
|
EMERALD = 'emerald',
|
||||||
|
TEAL = 'teal',
|
||||||
CYAN = 'cyan',
|
CYAN = 'cyan',
|
||||||
BLACK = 'black',
|
SKY = 'sky',
|
||||||
WHITE = 'white',
|
BLUE = 'blue',
|
||||||
PINK = 'pink',
|
INDIGO = 'indigo',
|
||||||
FUCHSIA = 'fuchsia',
|
|
||||||
VIOLET = 'violet',
|
VIOLET = 'violet',
|
||||||
}
|
PURPLE = 'purple',
|
||||||
|
FUCHSIA = 'fuchsia',
|
||||||
export interface Theme {
|
PINK = 'pink',
|
||||||
symbol: String
|
ROSE = 'rose',
|
||||||
name: String
|
|
||||||
colors: ColorsTheme[]
|
|
||||||
availability?: {
|
|
||||||
start: String
|
|
||||||
end: String
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the themes
|
|
||||||
const RainbowTheme: Theme = {
|
|
||||||
symbol: '🌈',
|
|
||||||
name: 'Rainbow',
|
|
||||||
colors: [
|
|
||||||
ColorsTheme.ORANGE,
|
|
||||||
ColorsTheme.YELLOW,
|
|
||||||
ColorsTheme.GREEN,
|
|
||||||
ColorsTheme.BLUE,
|
|
||||||
ColorsTheme.PURPLE,
|
|
||||||
ColorsTheme.ROSE,
|
|
||||||
ColorsTheme.RED,
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
const XMasTheme: Theme = {
|
|
||||||
symbol: '🎄',
|
|
||||||
name: 'Xmas',
|
|
||||||
colors: [ColorsTheme.RED, ColorsTheme.GREEN],
|
|
||||||
availability: {
|
|
||||||
start: '01/12',
|
|
||||||
end: '31/12',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const EasterTheme: Theme = {
|
|
||||||
symbol: '🐣',
|
|
||||||
name: 'Easter',
|
|
||||||
colors: [ColorsTheme.ROSE, ColorsTheme.YELLOW, ColorsTheme.CYAN],
|
|
||||||
availability: {
|
|
||||||
start: '01/04',
|
|
||||||
end: '12/04',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const BlackAndWhiteTheme: Theme = {
|
|
||||||
symbol: '📺',
|
|
||||||
name: 'B & W',
|
|
||||||
colors: [ColorsTheme.BLACK, ColorsTheme.WHITE],
|
|
||||||
}
|
|
||||||
|
|
||||||
const HalloweenTheme: Theme = {
|
|
||||||
symbol: '🎃',
|
|
||||||
name: 'Halloween',
|
|
||||||
colors: [
|
|
||||||
ColorsTheme.ORANGE,
|
|
||||||
ColorsTheme.BLACK,
|
|
||||||
ColorsTheme.GREEN,
|
|
||||||
ColorsTheme.PURPLE,
|
|
||||||
],
|
|
||||||
availability: {
|
|
||||||
start: '28/10',
|
|
||||||
end: '01/11',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const ValentineTheme: Theme = {
|
|
||||||
symbol: '💖',
|
|
||||||
name: 'Valentine',
|
|
||||||
colors: [
|
|
||||||
ColorsTheme.RED,
|
|
||||||
ColorsTheme.ROSE,
|
|
||||||
ColorsTheme.PINK,
|
|
||||||
ColorsTheme.FUCHSIA,
|
|
||||||
ColorsTheme.VIOLET,
|
|
||||||
],
|
|
||||||
availability: {
|
|
||||||
start: '12/02',
|
|
||||||
end: '16/02',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// List the themes
|
|
||||||
export enum THEMES {
|
|
||||||
RainbowTheme,
|
|
||||||
EasterTheme,
|
|
||||||
XMasTheme,
|
|
||||||
BlackAndWhiteTheme,
|
|
||||||
ValentineTheme,
|
|
||||||
HalloweenTheme,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Themes = {
|
|
||||||
[THEMES.RainbowTheme]: RainbowTheme,
|
|
||||||
[THEMES.EasterTheme]: EasterTheme,
|
|
||||||
[THEMES.XMasTheme]: XMasTheme,
|
|
||||||
[THEMES.BlackAndWhiteTheme]: BlackAndWhiteTheme,
|
|
||||||
[THEMES.ValentineTheme]: ValentineTheme,
|
|
||||||
[THEMES.HalloweenTheme]: HalloweenTheme,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
import {
|
|
||||||
defineConfig, presetAttributify, presetIcons,
|
|
||||||
presetTypography, presetUno, presetWind,
|
|
||||||
transformerDirectives, transformerVariantGroup,
|
|
||||||
} from 'unocss'
|
|
||||||
import { presetScrollbar } from 'unocss-preset-scrollbar'
|
|
||||||
import { ColorsTheme } from './types'
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
presets: [
|
|
||||||
presetUno(),
|
|
||||||
presetAttributify(),
|
|
||||||
presetIcons(),
|
|
||||||
presetTypography(),
|
|
||||||
presetScrollbar(),
|
|
||||||
presetWind({
|
|
||||||
dark: 'class',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
boxShadow: {
|
|
||||||
'header-active-light': 'inset 0 -1px 0 0 #eaeaea',
|
|
||||||
'header-active-dark': 'inset 0 -1px 0 0 #333',
|
|
||||||
'announcement-light': '0 0 0 1px rgba(0,0,0,.03), 0 2px 4px rgba(0,0,0,.05), 0 4px 16px rgba(0,0,0,.05)',
|
|
||||||
'announcement-dark': '0 0 0 1px rgba(150,150,150,.06), 0 2px 4px rgba(150,150,150,.1), 0 4px 16px rgba(150,150,150,.1)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
transformers: [
|
|
||||||
transformerVariantGroup(),
|
|
||||||
transformerDirectives(),
|
|
||||||
],
|
|
||||||
safelist: [
|
|
||||||
// Theme text colors
|
|
||||||
...Object.values(ColorsTheme).map(color => `text-${color}-500`),
|
|
||||||
...Object.values(ColorsTheme).map(color => `hover:text-${color}-500`),
|
|
||||||
...'bg-black dark:bg-white dark:text-black text-white'.split(' '),
|
|
||||||
|
|
||||||
// Theme background colors
|
|
||||||
...Object.values(ColorsTheme).map(color => `bg-${color}-500`),
|
|
||||||
...Object.values(ColorsTheme).map(color => `hover:bg-${color}-500`),
|
|
||||||
...'text-black dark:text-white'.split(' '),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
Reference in New Issue
Block a user