premier commit

This commit is contained in:
Valentin 2024-02-21 00:30:14 +01:00
commit f94e7681bf
27 changed files with 15584 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
# Local env files
.env
.env.*
!.env.example

75
README.md Normal file
View File

@ -0,0 +1,75 @@
# 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
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
```
## Development Server
Start the development server on `http://localhost:3000`:
```bash
# npm
npm run dev
# pnpm
pnpm run dev
# yarn
yarn dev
# bun
bun run dev
```
## Production
Build the application for production:
```bash
# npm
npm run build
# pnpm
pnpm run build
# yarn
yarn build
# bun
bun run build
```
Locally preview production build:
```bash
# npm
npm run preview
# pnpm
pnpm run preview
# yarn
yarn preview
# bun
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.

75
app.vue Normal file
View File

@ -0,0 +1,75 @@
<template>
<Header />
<main>
<NuxtPage />
</main>
</template>
<script setup>
const { getItems } = useDirectusItems();
const global = ref([]);
onMounted(async () => {
const items = await getItems({ collection: "Global" });
global.value = items;
});
provide('globalData', global);
useSeoMeta({
ogImage: '/card.jpg',
ogImageAlt: global.value.contact_image_titre,
twitterImage: '/card.jpg',
});
useHead({
htmlAttrs: {
lang: 'fr'
},
link: [
{
rel: 'icon',
type: 'image/png',
href: 'favicon.png'
}
]
});
</script>
<style lang="scss">
@font-face {
font-family: 'Latitude';
src: url('assets/fonts/latitude-webfont.woff2') format('woff2'),
url('assets/fonts/latitude-webfont.woff') format('woff');
font-weight: normal;
font-style: normal;
}
* {
margin: 0;
box-sizing: border-box;
font-family: 'Latitude', serif;
font-size: 1.1rem;
font-weight: normal;
text-decoration: none;
color: #0e312f;
}
body {
padding: 1rem;
header {
z-index: 1;
}
main {
z-index: 0;
}
}
.page-enter-active,
.page-leave-active {
transition: all 0.2s;
}
.page-enter-from,
.page-leave-to {
opacity: 0;
}
</style>

Binary file not shown.

Binary file not shown.

BIN
assets/images/after.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
assets/images/before.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
assets/images/close.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
assets/images/instagram.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

157
components/Header.vue Normal file
View File

@ -0,0 +1,157 @@
<template>
<header>
<nav>
<div>
<h1 :class="{ active: isActive('/') }">
<div class="hover_el_active" id="indexActive" aria-label="selected element"></div>
<NuxtLink to="/">mahée auffret</NuxtLink>
</h1>
</div>
<ul>
<li :class="{ active: isActive('/galerie') }">
<div class="hover_el_active" id="galeryActive" aria-label="selected element"></div>
<NuxtLink to="/galerie">galerie</NuxtLink>
</li>
<li :class="{ active: isActive('/magasin') }">
<div class="hover_el_active" id="shopActive" aria-label="selected element"></div>
<NuxtLink to="/magasin">magasin</NuxtLink>
</li>
<li :class="{ active: isActive('/contact') }">
<div class="hover_el_active" id="contactActive" aria-label="selected element"></div>
<NuxtLink to="/contact">contact</NuxtLink>
</li>
</ul>
</nav>
<div>
<a :href="globalData.instagram_link" target="_blank">
<img src="/assets/images/instagram.png" alt="Logo Instagram Dessiné" />
</a>
</div>
</header>
</template>
<script>
export default {
setup() {
const globalData = inject('globalData');
return { globalData };
},
methods: {
isActive(path) {
return this.$route.path === path;
},
}
}
</script>
<style lang="scss">
header {
width: 100%;
display: flex;
justify-content: space-between;
nav {
display: flex;
flex-direction: column;
h1 {
display: inline-block;
width: auto;
position: relative;
margin-top: 1rem;
> a {
position: relative;
padding: 10px;
padding-left: 0;
z-index: 1;
}
}
h1 .hover_el_active {
left: -40% !important;
width: 170% !important;
}
h1:hover .hover_el_active,
h1.active .hover_el_active {
opacity: 1;
}
ul {
display: flex;
list-style: none;
padding: 0;
margin-top: 1rem;
> li {
position: relative;
margin-left: 1.5rem;
> a {
position: relative;
padding: 10px;
z-index: 1;
}
}
> li:hover .hover_el_active,
> li.active .hover_el_active {
opacity: 1;
}
> li:first-of-type {
margin-left: 0;
}
}
.hover_el_active {
position: absolute;
top: -70%;
left: -15%;
width: 130%;
height: 240%;
background-size: contain !important;
background-repeat: no-repeat !important;
background-position: center !important;
z-index: 1;
opacity: 0;
transition: opacity 0.3s ease;
}
#indexActive {
background-image: url('/assets/images/hover-index.png');
}
#galeryActive {
background-image: url('/assets/images/hover-index.png');
left: -30%;
width: 160%;
}
#shopActive {
background-image: url('/assets/images/hover-index.png');
}
#contactActive {
background-image: url('/assets/images/hover-index.png');
}
}
> div {
width: 3rem;
z-index: 1;
img {
width: 100%;
transform: rotate(0deg);
transition: transform 0.3s ease-out;
}
img:hover {
transform: rotate(10deg);
}
}
}
@media screen and (min-width: 800px) {
header {
align-items: center;
nav {
flex-direction: row;
h1 {
margin-top: 0;
}
ul {
margin-top: 0;
> li:first-of-type {
margin-left: 2.5rem;
}
}
}
}
}
</style>

205
components/Projects.vue Normal file
View File

@ -0,0 +1,205 @@
<template>
<main>
<article v-for="content in contents" :key="content.id">
<div>
<NuxtImg
:src="img((content.image ? content.image : content.shop_image))"
:alt="content.titre"
format="webp"
placeholder
lazy
sizes="sm:40vw lg:30vw"
@click="displaySlider(content.id)" />
</div>
<div>
<p v-if="content.titre">{{ content.titre }}</p>
<p v-if="content.medium">{{ content.medium }}</p>
<p v-if="content.description">{{ content.description }}</p>
<p v-if="content.format">{{ content.format }}</p>
<p v-if="content.annee">{{ content.annee }}</p>
<p v-if="content.prix">{{ content.prix }}</p>
</div>
</article>
<swiper
:zoom="true"
:loop="true"
:modules="modules"
:navigation="true"
@slideChange="onSlideChange"
@swiper="onSwiper"
>
<swiper-slide v-for="content in contents" :key="content.id">
<div class="swiper-zoom-container">
<NuxtImg
:src="img((content.image ? content.image : content.shop_image))"
:alt="content.titre"
format="webp"
placeholder
lazy />
</div>
</swiper-slide>
<div class="swiper-button-close" @click="closeSlider()"></div>
</swiper>
</main>
</template>
<script>
import { A11y, Navigation, Zoom } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/vue";
import "swiper/css";
import 'swiper/css/zoom';
export default {
setup() {
const { getThumbnail : img } = useDirectusFiles();
const swiperInstance = ref(null);
const onSwiper = (swiper) => {
swiperInstance.value = swiper;
};
const onSlideChange = () => {};
const displaySlider = (index) => {
const body = document.querySelector('body');
body.style.overflowY = 'hidden';
const swiper = swiperInstance.value;
swiper.slideToLoop(index - 1);
const swiperEl = swiper.el;
swiperEl.style.display = "block";
setTimeout(() => {
swiperEl.style.opacity = 1;
}, 10);
}
const closeSlider = () => {
const body = document.querySelector('body');
body.style.overflowY = 'auto';
const swiperEl = document.querySelector('.swiper');
swiperEl.style.opacity = 0;
setTimeout(() => {
swiperEl.style.display = "none";
}, 300);
}
return {
onSwiper,
onSlideChange,
modules: [Navigation, A11y, Zoom],
displaySlider,
closeSlider,
img
};
},
components: {
Swiper,
SwiperSlide,
},
props: {
contents: Object
}
}
</script>
<style lang="scss">
main {
margin-top: 5vh;
article {
display: flex;
margin-bottom: 3vh;
> div:first-of-type {
width: 40vw;
img {
width: 100%;
cursor: pointer;
}
}
> div:last-of-type {
padding-left: 5vw;
width: 50vw;
p {
line-height: 1.2;
margin-bottom: 0.5vh;
}
}
}
.swiper {
top: 0;
left: 0;
width: 100%;
height: 100%;
backdrop-filter: blur(5px);
background-color: rgba(255, 255, 255, 0.5);
position: fixed;
display: none;
opacity: 0;
transition: opacity 0.3s ease-out;
.swiper-wrapper {
.swiper-slide {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
img{
width: 60%;
}
}
}
.swiper-button-prev,
.swiper-button-next,
.swiper-button-close {
z-index: 1;
position: absolute;
width: 2rem;
height: 2rem;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
cursor: pointer;
}
.swiper-button-prev {
left: 5vw;
bottom: calc(50vh - 1rem);
background-image: url(/assets/images/before.png);
}
.swiper-button-next {
right: 5vw;
bottom: calc(50vh - 1rem);
background-image: url(/assets/images/after.png);
}
.swiper-button-close {
right: 5vw;
top: 5vh;
background-image: url(/assets/images/close.png);
}
}
}
@media screen and (min-width: 800px) {
main {
article {
> div:first-of-type {
width: 25vw;
}
> div:last-of-type {
padding-left: 2vw;
}
}
.swiper .swiper-wrapper .swiper-slide img {
width: 40%;
}
}
}
@media screen and (min-width: 1200px) {
main {
article > div:first-of-type {
width: 15vw;
}
.swiper .swiper-wrapper .swiper-slide img {
width: 30%;
}
}
}
</style>

59
error.vue Normal file
View File

@ -0,0 +1,59 @@
<template>
<Header />
<div class="error-page">
<h1 v-if="error.statusCode === 404">Erreur 404</h1>
<p v-if="error.statusCode === 404">La page {{ error.url }} n'existe pas</p>
<NuxtImg
v-if="global.error_img"
:src="img(global.error_img)"
:alt="global.error_img_title"
format="webp"
placeholder
lazy
sizes="sm:100vw" />
</div>
</template>
<script>
export default {
setup() {
const { getItems } = useDirectusItems();
const { getThumbnail : img } = useDirectusFiles();
const global = ref([]);
onMounted(async () => {
const items = await getItems({ collection: "Global" });
global.value = items;
});
provide('globalData', global);
return {
global,
img
}
},
props: {
error: Object
}
}
</script>
<style scoped>
.error-page {
text-align: center;
margin: 3rem auto;
}
h1 {
margin-bottom: 1rem;
}
img {
width: 60vw;
margin: auto;
margin-top: 5rem;
}
</style>

25
nuxt.config.ts Normal file
View File

@ -0,0 +1,25 @@
export default defineNuxtConfig({
devtools: { enabled: true },
modules: [
'nuxt-directus',
'@nuxt/image',
'@nuxtjs/seo'
],
app: {
pageTransition: { name: 'page', mode: 'out-in' }
},
site: {
url: 'https://localhost:3000',
defaultLocale: 'fr',
name: 'Mahée Auffret',
description: 'Portfolio de l\'artiste-peintre basée à Rennes Mahée Auffret'
},
directus: {
url: "http://localhost:8055/",
token: process.env.DIRECTUS_API_TOKEN
},
image: {
domains: ['http://localhost:8055/'],
format: ['webp'],
}
})

14727
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

24
package.json Normal file
View File

@ -0,0 +1,24 @@
{
"name": "nuxt-app",
"private": true,
"type": "module",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"dependencies": {
"@nuxt/image": "^1.3.0",
"nuxt": "^3.10.1",
"nuxt-directus": "^5.6.0",
"swiper": "^11.0.6",
"vue": "^3.4.15",
"vue-router": "^4.2.5"
},
"devDependencies": {
"@nuxtjs/seo": "^2.0.0-rc.8",
"sass": "^1.71.0"
}
}

67
pages/contact.vue Normal file
View File

@ -0,0 +1,67 @@
<template>
<main id="contact">
<div v-if="globalData.contact_image">
<NuxtImg
:src="img(globalData.contact_image)"
:alt="globalData.contact_image_titre"
format="webp"
placeholder
lazy
sizes="sm:40vw" />
</div>
<div>
<p>{{ globalData.contact_texte }}</p>
<a :href="'mailto:' + globalData.email">{{ globalData.email }}</a>
</div>
</main>
</template>
<script setup>
const globalData = inject('globalData');
const { getThumbnail : img } = useDirectusFiles();
</script>
<style lang="scss">
#contact {
display: flex;
flex-direction: column;
> div:first-of-type {
width: 40vw;
> img {
width: 100%;
}
}
> div:last-of-type {
margin-top: 2rem;
> p {
line-height: 1.2;
margin-bottom: 1.5rem;
}
}
}
@media screen and (min-width: 800px) {
#contact {
flex-direction: row;
> div:first-of-type {
width: 30vw;
}
> div:last-of-type {
width: 40vw;
margin-left: 2rem;
align-self: flex-end;
}
}
}
@media screen and (min-width: 1200px) {
#contact {
> div:first-of-type {
width: 15vw;
}
> div:last-of-type {
width: 30vw;
}
}
}
</style>

25
pages/galerie.vue Normal file
View File

@ -0,0 +1,25 @@
<template>
<Projects :contents="galerie" />
</template>
<script>
import Projects from '@/components/Projects.vue';
export default {
setup() {
const { getItems } = useDirectusItems();
const galerie = ref([]);
onMounted(async () => {
const items = await getItems({ collection: "galerie" });
galerie.value = items;
});
return {
galerie
};
},
components: {
Projects
}
};
</script>

83
pages/index.vue Normal file
View File

@ -0,0 +1,83 @@
<template>
<main>
<div class="indexImg" v-for="image in itemsAccueil" :key="image.id">
<NuxtImg
:src="img(image.image_accueil)"
:alt="image.titre"
format="webp"
placeholder
lazy
sizes="sm:150vw" />
</div>
</main>
</template>
<script setup>
const { getItems } = useDirectusItems();
const itemsAccueil = await getItems({
collection: "images_accueil"
});
const { getThumbnail : img } = useDirectusFiles();
onMounted(() => {
const imgs = document.querySelectorAll('.indexImg img');
const showingTime = 5000, transitionTime = 2000;
for (let img of imgs) {
img.addEventListener('click', function() {
nextSlide();
resetTimer();
});
img.style.transition = `opacity ${transitionTime / 1000}s ease-out`;
}
imgs[0].style.opacity = 1;
let diapoTimer = setInterval(nextSlide, showingTime + transitionTime);
let index = 1;
function nextSlide() {
if (index === 0) {
imgs[imgs.length - 1].style.opacity = 0;
imgs[index].style.opacity = 1;
} else {
imgs[index - 1].style.opacity = 0;
imgs[index].style.opacity = 1;
}
if (index === imgs.length - 1) {
index = 0;
} else {
index ++;
}
}
function resetTimer() {
clearInterval(diapoTimer);
diapoTimer = setInterval(nextSlide, showingTime + transitionTime);
}
});
</script>
<style lang="scss">
.indexImg {
z-index: -1;
position: absolute;
overflow: hidden;
top: 0;
left: 0;
width: 100%;
height: 100%;
img {
object-fit: cover;
width: 100%;
height: 100%;
opacity: 0;
}
}
</style>

31
pages/magasin.vue Normal file
View File

@ -0,0 +1,31 @@
<template>
<main>
<p>{{ globalData.magasin_explication }}</p>
<Projects :contents="magasin" />
</main>
</template>
<script>
import Projects from '@/components/Projects.vue';
export default {
setup() {
const { getItems } = useDirectusItems();
const magasin = ref([]);
onMounted(async () => {
const items = await getItems({ collection: "magasin" });
magasin.value = items;
});
const globalData = inject('globalData');
return {
globalData,
magasin
};
},
components: {
Projects
}
};
</script>

BIN
public/card.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

BIN
public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

3
server/tsconfig.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "../.nuxt/tsconfig.server.json"
}

4
tsconfig.json Normal file
View File

@ -0,0 +1,4 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}