first commit

This commit is contained in:
TDLaouer 2025-07-12 14:17:55 +02:00
commit f74eb98414
21 changed files with 8751 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

3
.prettierrc Normal file
View File

@ -0,0 +1,3 @@
{
"plugins": ["prettier-plugin-tailwindcss"]
}

75
README.md Normal file
View File

@ -0,0 +1,75 @@
# Nuxt Minimal Starter
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
## Setup
Make sure to install 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 dev
# yarn
yarn dev
# bun
bun run dev
```
## Production
Build the application for production:
```bash
# npm
npm run build
# pnpm
pnpm build
# yarn
yarn build
# bun
bun run build
```
Locally preview production build:
```bash
# npm
npm run preview
# pnpm
pnpm preview
# yarn
yarn preview
# bun
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.

10
app.vue Normal file
View File

@ -0,0 +1,10 @@
<template>
<NuxtLoadingIndicator color="#2563eb" :height="4" />
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
<style>
@import "primeicons/primeicons.css";
</style>

1
assets/css/main.css Normal file
View File

@ -0,0 +1 @@
@import "tailwindcss";

98
components/CardView.vue Normal file
View File

@ -0,0 +1,98 @@
<template>
<motion.img
:src="props.card.url"
draggable="false"
class="h-[60cqh] max-h-[800px] w-[400px] origin-bottom rounded-lg object-cover hover:cursor-grab active:cursor-grabbing md:w-[600px]"
:style="{
zIndex: props.isFront ? 10 : props.z,
gridRow: 1,
gridColumn: 1,
transition: '0.125s transform',
x,
opacity,
rotate,
boxShadow: isFront
? '0 20px 25px -5px rgb(0 0 0 / 0.5), 0 8px 10px -6px rgb(0 0 0 / 0.5)'
: undefined,
}"
alt="Picture of the author"
:drag="dragValue"
:drag-constraints="{ left: 0, right: 0 }"
:on-drag-end="handleDragEnd"
:animate="{
scale: props.isFront ? 1 : 0.98,
}"
/>
<div v-if="props.isFront" class="flex justify-between">
<!-- Pass Button -->
<button
class="mx-12 flex h-24 w-42 flex-col items-center justify-center rounded-xl bg-violet-500 text-4xl text-white shadow-lg transition-colors duration-150 hover:bg-violet-600 focus:ring-4 focus:ring-violet-300 focus:outline-none"
aria-label="Pass"
type="button"
@click="swipeLeft"
>
<span class="text-base font-semibold">Pass</span>
</button>
<!-- Smash Button -->
<button
class="mx-12 flex h-24 w-42 flex-col items-center justify-center rounded-xl bg-teal-500 text-4xl text-white shadow-lg transition-colors duration-150 hover:bg-teal-600 focus:ring-4 focus:ring-teal-300 focus:outline-none"
aria-label="Smash"
type="button"
@click="swipeRight"
>
<span class="text-base font-semibold">Smash</span>
</button>
</div>
</template>
<script setup lang="ts">
import { animate, motion, useMotionValue, useTransform } from "motion-v";
import type { PropType } from "vue";
import { useCardStore } from "~/store/Card";
import type { Card } from "~/types/Card";
const cardStore = useCardStore();
const props = defineProps({
card: {
type: Object as PropType<Card>,
required: true,
},
isFront: {
type: Boolean,
required: true,
},
z: {
type: Number,
required: true,
},
});
const dragValue = computed(() => (props.isFront ? "x" : false));
const x = useMotionValue(0);
const opacity = useTransform(x, [-150, 0, 150], [0, 1, 0]);
const rotateRaw = useTransform(x, [-150, 150], [-18, 18]);
const rotate = useTransform(() => {
const offset = props.isFront ? 0 : props.card.id % 2 ? 4 : -4;
return `${rotateRaw.get() + offset}deg`;
});
const handleDragEnd = () => {
if (Math.abs(x.get()) > 50) {
cardStore.removeCard(props.card.id);
}
};
async function swipeRight() {
await animate(x, 300, { duration: 0.3 });
cardStore.removeCard(props.card.id);
}
async function swipeLeft() {
await animate(x, -300, { duration: 0.3 });
cardStore.removeCard(props.card.id);
}
</script>

4
eslint.config.mjs Normal file
View File

@ -0,0 +1,4 @@
// @ts-check
import withNuxt from './.nuxt/eslint.config.mjs'
export default withNuxt()

0
middleware/auth.ts Normal file
View File

38
nuxt.config.ts Normal file
View File

@ -0,0 +1,38 @@
import Aura from "@primeuix/themes/aura";
import tailwindcss from "@tailwindcss/vite";
export default defineNuxtConfig({
compatibilityDate: "2025-05-15",
devtools: { enabled: true },
modules: [
"@nuxt/eslint",
"@nuxt/image",
"@nuxt/ui",
"@primevue/nuxt-module",
"motion-v/nuxt",
"@pinia/nuxt",
"nuxt-toast",
],
css: ["~/assets/css/main.css"],
vite: { plugins: [tailwindcss()] },
app: {
baseURL: process.env.BASE_URL || "/",
},
primevue: {
components: {
exclude: ["Form", "FormField"],
},
options: {
ripple: true,
inputVariant: "filled",
theme: {
preset: Aura,
options: {
prefix: "p",
darkModeSelector: "system",
cssLayer: false,
},
},
},
},
});

40
package.json Normal file
View File

@ -0,0 +1,40 @@
{
"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/eslint": "1.5.2",
"@nuxt/image": "1.10.0",
"@nuxt/ui": "3.2.0",
"@pinia/nuxt": "0.11.1",
"@primeuix/themes": "^1.2.1",
"@tailwindcss/vite": "^4.1.11",
"autoprefixer": "^10.4.21",
"eslint": "^9.0.0",
"izitoast": "^1.4.0",
"motion-v": "^1.5.0",
"nuxt": "^3.17.6",
"nuxt-toast": "1.1.4",
"pinia": "^3.0.3",
"primeicons": "^7.0.0",
"primevue": "^4.3.6",
"tailwindcss": "^4.1.11",
"tailwindcss-primeui": "^0.6.1",
"typescript": "^5.6.3",
"vue": "^3.5.17",
"vue-router": "^4.5.1"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
"devDependencies": {
"@primevue/nuxt-module": "^4.3.6",
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.6.14"
}
}

20
pages/game.vue Normal file
View File

@ -0,0 +1,20 @@
<template>
<div class="grid min-h-screen place-items-center">
<CardView
v-for="(card, index) in cardStore.cards"
:key="card.id"
:card="card"
:is-front="index === cardStore.cards.length - 1"
:z="index"
/>
</div>
</template>
<script setup lang="ts">
import CardView from "~/components/CardView.vue";
import { useCardStore } from "~/store/Card";
const route = useRoute();
const collection = route.query.collection;
const cardStore = useCardStore();
</script>

94
pages/index.vue Normal file
View File

@ -0,0 +1,94 @@
<template>
<div class="flex min-h-screen flex-col items-center justify-center">
<h1 class="mb-8 text-3xl font-bold">Select a collection to begin</h1>
<ul class="w-full max-w-md space-y-4">
<li
v-for="collection in cardStore.collections"
:key="collection"
class="flex items-center justify-between rounded p-4 shadow"
>
<span class="text-lg font-medium">{{ collection }}</span>
<!-- Large Play Button -->
<Button
icon="pi pi-play"
rounded
class="flex items-center justify-center bg-blue-600 text-white"
:class="{
'!h-32 !w-32': activeItem === collection,
'!h-16 !w-16': activeItem !== collection,
}"
style="
transition:
width 0.3s,
height 0.3s;
"
@click="play(collection)"
/>
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { useCardStore } from "~/store/Card";
const cardStore = useCardStore();
cardStore.collections = ["Test", "Test1"];
cardStore.cards = [
{
id: 1,
name: "Pipi",
url: "https://images.unsplash.com/photo-1542291026-7eec264c27ff?q=80&w=2370&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
{
id: 2,
name: "Pipi",
url: "https://images.unsplash.com/photo-1512374382149-233c42b6a83b?q=80&w=2235&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
{
id: 3,
name: "Pipi",
url: "https://images.unsplash.com/photo-1539185441755-769473a23570?q=80&w=2342&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
{
id: 4,
name: "Pipi",
url: "https://images.unsplash.com/photo-1549298916-b41d501d3772?q=80&w=2224&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
{
id: 5,
name: "Pipi",
url: "https://images.unsplash.com/photo-1516478177764-9fe5bd7e9717?q=80&w=2340&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
{
id: 6,
name: "Pipi",
url: "https://images.unsplash.com/photo-1570464197285-9949814674a7?q=80&w=2273&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
{
id: 7,
name: "Pipi",
url: "https://images.unsplash.com/photo-1578608712688-36b5be8823dc?q=80&w=2187&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
{
id: 8,
name: "Pipi",
url: "https://images.unsplash.com/photo-1505784045224-1247b2b29cf3?q=80&w=2340&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
];
const activeItem = ref();
const play = (collection: string) => {
navigateTo({ path: "game", query: { collection: collection } });
};
</script>

68
pages/result.vue Normal file
View File

@ -0,0 +1,68 @@
<template>
<div class="flex min-h-screen flex-col items-center gap-10">
<div class="flex items-start justify-center space-x-8 p-8">
<!-- First Grid -->
<div class="w-1/2">
<h2 class="mb-4 text-xl font-bold">You smashed</h2>
<div
class="grid grid-cols-1 gap-2 lg:grid-cols-3 xl:grid-cols-5 xl:gap-4"
>
<div
v-for="(img, idx) in imagesListOne"
:key="idx"
class="flex items-center justify-center rounded p-2 shadow"
>
<img
:src="img"
alt="Image from Smash List"
class="h-32 w-full rounded object-cover"
/>
</div>
</div>
</div>
<!-- Second Grid -->
<div class="w-1/2">
<h2 class="mb-4 text-xl font-bold">You passed</h2>
<div
class="grid grid-cols-1 gap-2 lg:grid-cols-3 xl:grid-cols-5 xl:gap-4"
>
<div
v-for="(img, idx) in imagesListTwo"
:key="idx"
class="flex items-center justify-center rounded p-2 shadow"
>
<img
:src="img"
alt="Image from pass list"
class="h-32 w-full rounded object-cover"
/>
</div>
</div>
</div>
</div>
<Button label="Return" class="h-16 w-64" />
</div>
</template>
<script setup>
const imagesListOne = [
// Replace with your image URLs
"https://picsum.photos/id/1011/200/200",
"https://picsum.photos/id/1012/800/400",
"https://picsum.photos/id/1013/200/200",
"https://picsum.photos/id/1014/200/200",
"https://picsum.photos/id/1015/200/200",
"https://picsum.photos/id/1016/200/200",
// ...more images
];
const imagesListTwo = [
"https://picsum.photos/id/1021/200/200",
"https://picsum.photos/id/1022/200/200",
"https://picsum.photos/id/1023/200/200",
"https://picsum.photos/id/1024/200/200",
"https://picsum.photos/id/1025/200/200",
"https://picsum.photos/id/1026/200/200",
// ...more images
];
</script>

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

2
public/robots.txt Normal file
View File

@ -0,0 +1,2 @@
User-Agent: *
Disallow:

3
server/tsconfig.json Normal file
View File

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

26
store/Card.ts Normal file
View File

@ -0,0 +1,26 @@
import { defineStore } from "pinia";
import type { Card } from "~/types/Card";
export const useCardStore = defineStore("cardStore", {
state: () => ({
collections: [] as string[],
cards: [] as Card[],
}),
actions: {
async fetchCollections() {
const { data: collections } =
await useFetch<string[]>("/api/collections");
this.collections = collections.value ?? [];
},
async fetchCardsByCollection(collectionName: string) {
const response = useFetch<Card[]>(`/api/cards/${collectionName}`);
this.cards = response.data.value ?? [];
},
removeCard(id: number) {
const index = this.cards.findIndex((card) => card.id === id);
if (index !== -1) {
this.cards.splice(index, 1);
}
},
},
});

11
tailwind.config.js Normal file
View File

@ -0,0 +1,11 @@
/** @type {import('tailwindcss').Config} */
import PrimeUI from 'tailwindcss-primeui';
export default {
content: [],
theme: {
extend: {},
},
plugins: [PrimeUI],
}

4
tsconfig.json Normal file
View File

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

5
types/Card.ts Normal file
View File

@ -0,0 +1,5 @@
export type Card = {
id: number;
url: string;
name: string;
};

8225
yarn.lock Normal file

File diff suppressed because it is too large Load Diff