This commit is contained in:
2023-08-11 00:49:30 +02:00
parent 9703d24784
commit 3dcd5f1ef6
33 changed files with 4010 additions and 2699 deletions

View File

@@ -1,6 +1,5 @@
{
"extends": [
"@antfu",
"@unocss"
"@antfu"
]
}

9
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,9 @@
{
"files.associations": {
"*.css": "tailwindcss"
},
"editor.quickSuggestions": {
"strings": true
},
"tailwindCSS.experimental.configFile": "tailwind.config.ts"
}

View File

@@ -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.

View File

@@ -10,32 +10,37 @@ export default defineNuxtConfig({
],
modules: [
'@nuxthq/ui',
'@pinia/nuxt',
'@pinia-plugin-persistedstate/nuxt',
'@unocss/nuxt',
'@nuxt/devtools',
'@nuxtjs/color-mode',
'@nuxtjs/fontaine',
'@vueuse/nuxt',
'@nuxt/content',
'nuxt-icon',
],
colorMode: {
preference: 'system',
preference: 'light',
fallback: 'light',
classPrefix: '',
classSuffix: '',
},
components: [
{
path: '~/components',
pathPrefix: false,
},
],
tailwindcss: {
viewer: true,
},
ui: {
icons: 'all',
},
devtools: {
enabled: true,
vscode: {
enabled: true,
startOnBoot: true,
port: 3001,
reuseExistingServer: true,
mode: 'tunnel',
},
},
})

View File

@@ -11,39 +11,34 @@
"lint:fix": "eslint . --fix"
},
"dependencies": {
"@nuxt/content": "^2.6.0",
"@nuxt/content": "^2.7.2",
"@nuxt/image": "^0.7.1",
"@nuxtjs/color-mode": "^3.2.0",
"@pinia/nuxt": "^0.4.9",
"@prisma/client": "^4.13.0",
"@trpc/client": "^10.21.2",
"@trpc/server": "^10.21.2",
"@vercel/analytics": "^1.0.0",
"@vueuse/motion": "^2.0.0-beta.12",
"nuxt-icon": "^0.4.0",
"pinia": "^2.0.35",
"sass": "^1.62.1",
"superjson": "^1.12.3",
"trpc-nuxt": "^0.9.0",
"unocss": "^0.51.8",
"@pinia/nuxt": "0.4.11",
"@prisma/client": "^5.0.0",
"@trpc/client": "10.35.0",
"@trpc/server": "^10.35.0",
"@vercel/analytics": "^1.0.1",
"@vueuse/motion": "2.0.0",
"pinia": "^2.1.4",
"postcss-custom-properties": "13.3.0",
"sass": "^1.64.1",
"superjson": "^1.13.1",
"tailwindcss": "^3.3.3",
"trpc-nuxt": "^0.10.6",
"zod": "^3.21.4"
},
"devDependencies": {
"@antfu/eslint-config": "0.26.3",
"@nuxt/devtools": "^0.4.2",
"@nuxtjs/fontaine": "^0.2.5",
"@pinia-plugin-persistedstate/nuxt": "^1.1.1",
"@types/node": "^18",
"@unocss/eslint-config": "^0.51.8",
"@unocss/nuxt": "^0.51.8",
"@unocss/transformer-directives": "^0.51.12",
"@unocss/transformer-variant-group": "^0.51.8",
"@vueuse/core": "^10.1.0",
"@vueuse/nuxt": "^10.1.0",
"eslint": "^8.39.0",
"nuxt": "^3.4.2",
"prisma": "^4.13.0",
"typescript": "^5.0.4",
"unocss-preset-scrollbar": "^0.2.1"
"@antfu/eslint-config": "0.39.8",
"@iconify/json": "2.2.94",
"@nuxt/devtools": "0.7.1",
"@nuxthq/ui": "^2.6.0",
"@pinia-plugin-persistedstate/nuxt": "1.1.1",
"@types/node": "20.4.4",
"@vueuse/core": "^10.2.1",
"@vueuse/nuxt": "^10.2.1",
"eslint": "^8.45.0",
"nuxt": "^3.6.5",
"prisma": "^5.0.0",
"typescript": "5.1.6"
}
}

22
src/app.config.ts Normal file
View 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
View 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 }
},
}

View File

@@ -1,5 +1,4 @@
body {
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;
}

View 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
}
}

View File

@@ -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>

View 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>

View File

@@ -1,5 +0,0 @@
<template>
<footer>
Footer
</footer>
</template>

View File

@@ -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>

View 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>

View File

@@ -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>

View 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>

View 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>

View 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>

View File

@@ -0,0 +1,13 @@
<script lang="ts" setup>
</script>
<template>
<div>
LOGO
</div>
</template>
<style>
</style>

View 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>

View 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>

View File

@@ -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,
}
}

View File

@@ -1,9 +1,16 @@
<template>
<Header />
<slot />
<Footer />
</template>
<CommandPalette />
<div class="fixed inset-0 flex justify-center sm:px-8">
<div class="flex w-full max-w-7xl lg:px-8">
<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>
@import '@unocss/reset/tailwind.css';
</style>
<UNotifications />
</template>

13
src/pages/about.vue Normal file
View File

@@ -0,0 +1,13 @@
<script setup>
</script>
<template>
<div>
ABOUT
</div>
</template>
<style>
</style>

View File

@@ -1,13 +1,13 @@
<script setup lang="ts">
import { useThemeStore } from '~/store/theme'
const { swapColor } = useThemeStore()
onMounted(() => swapColor())
const days = ref(0)
useHead({
title: 'Arthur Danjou • Software Engineer and Maths Lover',
})
</script>
<template>
<section>
Hey
<Announcement />
<MainBanner />
<NewsletterCard />
</section>
</template>

View File

@@ -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>

View File

@@ -1,5 +1,5 @@
import { inject } from '@vercel/analytics'
export default () => {
export default defineNuxtPlugin(() => {
inject()
}
})

28
src/store/color.ts Normal file
View 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,
},
)

View File

@@ -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
View 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
View File

@@ -1,117 +1,19 @@
// Define a theme
export enum ColorsTheme {
ORANGE = 'orange',
YELLOW = 'yellow',
GREEN = 'green',
BLUE = 'blue',
PURPLE = 'purple',
ROSE = 'rose',
RED = 'red',
ORANGE = 'orange',
AMBER = 'amber',
YELLOW = 'yellow',
LIME = 'lime',
GREEN = 'green',
EMERALD = 'emerald',
TEAL = 'teal',
CYAN = 'cyan',
BLACK = 'black',
WHITE = 'white',
PINK = 'pink',
FUCHSIA = 'fuchsia',
SKY = 'sky',
BLUE = 'blue',
INDIGO = 'indigo',
VIOLET = 'violet',
}
export interface Theme {
symbol: String
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,
PURPLE = 'purple',
FUCHSIA = 'fuchsia',
PINK = 'pink',
ROSE = 'rose',
}

View File

@@ -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(' '),
],
})

5476
yarn.lock

File diff suppressed because it is too large Load Diff