multi carte
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled

This commit is contained in:
Nyavokevin 2025-09-26 17:57:55 +03:00
parent cf89535c14
commit fa94499268
9 changed files with 559 additions and 41 deletions

View File

@ -30,7 +30,7 @@ class StripeController extends Controller
$priceIds = [
3 => 'price_1S51zxGaZ3yeYkzWYb0wSt4j',
4 => 'price_1S5464GaZ3yeYkzWh8RuJfab',
21 => 'price_1S5464GaZ3yeYkzWh8RuJfab',
];
if (!isset($priceIds[$count])) {

View File

@ -8,9 +8,13 @@
@custom-variant dark (&:is(.dark *));
@theme inline {
--font-heading: 'Montserrat', sans-serif;
--font-body: 'Montserrat', sans-serif;
--font-accent: 'Poppins', cursive;
--font-title: 'Montserrat ExtraBold', sans-serif;
--font-sub-title: 'Poppins', cursive;
--font-text: 'Roboto', sans-serif;
--font-body: var(--font-text);
--font-heading: var(--font-title);
--font-accent: var(--font-sub-title);
--font-sans: var(--font-body);
@ -95,10 +99,10 @@
:root {
/* Brand variables */
--c-black: #000000;
--c-green: #9ACE8A;
--c-purple: #7D5BA6; /* softer purple primary */
--c-gold: #F0BA2D;
--c-white: #FFFFFF;
--c-green: #9ace8a;
--c-purple: #7d5ba6; /* softer purple primary */
--c-gold: #f0ba2d;
--c-white: #ffffff;
/* Additional accent stops for hero gradient */
--c-magenta: #d94f93;
--c-red: #e54b4d;
@ -124,7 +128,7 @@
--muted: #eee7f7;
--muted-foreground: #5b4b72;
--accent: #B89BD6;
--accent: #b89bd6;
--accent-foreground: #1f1630;
--destructive: #ef4444;
@ -135,7 +139,7 @@
--ring: var(--c-purple);
--chart-1: var(--c-purple);
--chart-2: #B89BD6;
--chart-2: #b89bd6;
--chart-3: var(--c-green);
--chart-4: #7a6b91;
--chart-5: #523f6a;
@ -147,7 +151,7 @@
--sidebar-foreground: #1f1630;
--sidebar-primary: var(--c-purple);
--sidebar-primary-foreground: var(--c-white);
--sidebar-accent: #B89BD6;
--sidebar-accent: #b89bd6;
--sidebar-accent-foreground: #1f1630;
--sidebar-border: #e2d7f2;
--sidebar-ring: var(--c-purple);
@ -173,7 +177,7 @@
--muted: #3a2d49;
--muted-foreground: #c8bfe0;
--accent: #B89BD6;
--accent: #b89bd6;
--accent-foreground: #1f1630;
--destructive: #ff7676;
@ -184,7 +188,7 @@
--ring: #9c7ca5;
--chart-1: var(--c-purple);
--chart-2: #B89BD6;
--chart-2: #b89bd6;
--chart-3: var(--c-green);
--chart-4: #7a6b91;
--chart-5: #523f6a;
@ -193,7 +197,7 @@
--sidebar-foreground: #f1ecf7;
--sidebar-primary: var(--c-purple);
--sidebar-primary-foreground: var(--c-white);
--sidebar-accent: #B89BD6;
--sidebar-accent: #b89bd6;
--sidebar-accent-foreground: #1f1630;
--sidebar-border: #4c3a60;
--sidebar-ring: #9c7ca5;
@ -229,8 +233,20 @@
font-family: var(--font-accent), cursive;
}
.font-title {
font-family: var(--font-title), sans-serif;
}
.font-sub-title {
font-family: var(--font-sub-title), cursive;
}
.font-text {
font-family: var(--font-text), sans-serif;
}
.citadel-script {
font-family: var(--font-accent);
font-family: var(--font-accent), cursive;
}
.bg-blur {
@ -250,7 +266,7 @@
/* Softer lilac gradient for headings */
.text-accent-gradient {
background: linear-gradient(45deg, #B89BD6, #7D5BA6);
background: linear-gradient(45deg, #b89bd6, #7d5ba6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
@ -300,3 +316,11 @@
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Roboto';
src: url('@/fonts/Roboto/static/Roboto-Medium.ttf') format('truetype');
font-weight: 400;
font-style: normal;
font-display: swap;
}

View File

@ -61,7 +61,7 @@
<style scoped>
.citadel-script {
font-family: 'Dancing Script', cursive;
font-family: var(--font-sub-title), cursive;
font-weight: 700;
}

View File

@ -2,7 +2,7 @@
import CardShuffleTemplate from '@/components/template/CardShuffleTemplate.vue';
import { Card } from '@/types/cart';
import { router } from '@inertiajs/vue3';
import { computed, ref, watchEffect } from 'vue';
import { computed, onMounted, ref, watchEffect } from 'vue';
const props = defineProps<{
drawCount?: number; // Optional: number of cards expected to be drawn
@ -20,9 +20,53 @@ const drawingIndex = ref<number | null>(null);
const revealedCards = ref<Card[]>([]);
const isFlipped = ref<boolean[]>([]);
const isRestacking = ref(false);
const isAutoDrawing = ref(false);
const autoDrawProgress = ref(0);
const remainingDeckSize = computed(() => Math.max(0, deckSize - revealedCards.value.length));
const isFinished = computed(() => !!props.drawnCards && revealedCards.value.length >= props.drawnCards.length);
const shouldAutoDraw = computed(() => props.drawnCards?.length > 15);
console.log(props.drawnCards?.length);
// Auto-draw cards with beautiful animation
const startAutoDraw = async () => {
if (!props.drawnCards || isAutoDrawing.value) return;
isAutoDrawing.value = true;
isSpread.value = false;
const totalCards = props.drawnCards.length;
const delayBetweenCards = 200; // ms between each card
for (let i = 0; i < totalCards; i++) {
if (!isAutoDrawing.value) break; // Allow cancellation
autoDrawProgress.value = (i / totalCards) * 100;
// Add card with animation
const nextCard = props.drawnCards[i];
revealedCards.value.push(nextCard);
isFlipped.value.push(false);
// Wait before next card
if (i < totalCards - 1) {
await new Promise((resolve) => setTimeout(resolve, delayBetweenCards));
}
}
autoDrawProgress.value = 100;
isAutoDrawing.value = false;
// Auto-flip all cards after drawing
setTimeout(() => {
for (let i = 0; i < revealedCards.value.length; i++) {
setTimeout(() => {
isFlipped.value[i] = true;
}, i * 150);
}
}, 500);
};
const startDraw = (index: number) => {
if (!props.drawnCards || revealedCards.value.length >= props.drawnCards.length) return;
@ -50,9 +94,15 @@ const startDraw = (index: number) => {
};
const handleDeckClick = () => {
if (isDrawing.value) return;
if (isDrawing.value || isAutoDrawing.value) return;
if (isFinished.value) return;
// If auto-draw is available and not started, start it
if (shouldAutoDraw.value && !isAutoDrawing.value && revealedCards.value.length === 0) {
startAutoDraw();
return;
}
// First click spreads the deck
if (!isSpread.value) {
isSpread.value = true;
@ -75,7 +125,7 @@ const handleDeckClick = () => {
const onCardClick = (i: number, e?: MouseEvent) => {
// Only handle and stop propagation when spread; otherwise let the deck click handler spread the deck
if (!isSpread.value || isDrawing.value) return;
if (!isSpread.value || isDrawing.value || isAutoDrawing.value) return;
if (e) e.stopPropagation();
if (!props.drawnCards || revealedCards.value.length >= props.drawnCards.length) return;
startDraw(i);
@ -116,12 +166,26 @@ watchEffect(() => {
drawingIndex.value = null;
revealedCards.value = [];
isFlipped.value = [];
isAutoDrawing.value = false;
autoDrawProgress.value = 0;
} else {
isSpread.value = false;
isDrawing.value = false;
drawingIndex.value = null;
revealedCards.value = [];
isFlipped.value = [];
isAutoDrawing.value = false;
autoDrawProgress.value = 0;
}
});
// Auto-start drawing if conditions are met
onMounted(() => {
if (shouldAutoDraw.value && props.drawnCards && props.drawnCards.length > 0) {
// Small delay to allow initial render
setTimeout(() => {
startAutoDraw();
}, 800);
}
});
@ -145,6 +209,15 @@ const setDrawnCards = (cardData: Card[]) => {
drawingIndex.value = null;
revealedCards.value = [];
isFlipped.value = new Array(cardData.length).fill(false);
isAutoDrawing.value = false;
autoDrawProgress.value = 0;
// Auto-start if conditions are met
if (shouldAutoDraw.value) {
setTimeout(() => {
startAutoDraw();
}, 800);
}
}
};
@ -155,15 +228,36 @@ defineExpose({ setDrawnCards });
<CardShuffleTemplate>
<template #card-shuffle-slot>
<div class="card-container">
<!-- Auto-draw progress indicator -->
<div v-if="shouldAutoDraw && isAutoDrawing" class="auto-draw-progress">
<div class="progress-bar">
<div class="progress-fill" :style="{ width: `${autoDrawProgress}%` }"></div>
</div>
<div class="progress-text">Distribution automatique en cours... {{ Math.round(autoDrawProgress) }}%</div>
</div>
<!-- Always show the card stack -->
<transition class="card-stack-fade">
<div
class="card-stack relative mt-6 mb-8 flex h-[520px] w-[320px] items-center justify-center rounded-2xl border border-[var(--c-purple)]/30 bg-gradient-to-b from-[var(--card)] to-[var(--card)]/90 p-4 shadow-xl ring-1 ring-[var(--c-purple)]/40"
:class="{ spread: isSpread, drawing: isDrawing, restacking: isRestacking, finished: isFinished }"
:class="{
spread: isSpread,
drawing: isDrawing,
restacking: isRestacking,
finished: isFinished,
'auto-drawing': isAutoDrawing,
}"
@click="handleDeckClick"
>
<div class="pointer-events-none absolute -top-16 -left-16 h-40 w-40 rounded-full bg-[var(--c-purple)]/20 blur-3xl"></div>
<div class="pointer-events-none absolute -right-16 -bottom-16 h-40 w-40 rounded-full bg-[var(--c-gold)]/15 blur-3xl"></div>
<!-- Auto-draw prompt -->
<div v-if="shouldAutoDraw && !isAutoDrawing && revealedCards.length === 0" class="auto-draw-prompt">
<div class="prompt-icon"></div>
<div class="prompt-text">Cliquez pour une distribution automatique</div>
</div>
<div v-for="i in remainingDeckSize" :key="i" class="card group/card" :style="getCardStyle(i)" @click="onCardClick(i, $event)">
<div class="card-back">
<div class="card-inner-content">
@ -173,11 +267,12 @@ defineExpose({ setDrawnCards });
</div>
</div>
</transition>
<!-- Show result only after clicking and when drawnCards is available -->
<transition class="card-result-slide">
<div v-if="revealedCards.length > 0" class="cards-result-container">
<div v-for="(card, index) in revealedCards" :key="index" class="card-result-wrapper">
<div class="result-card" :class="{ flipped: isFlipped[index] }" @click="flipCard(index)">
<div v-if="revealedCards.length > 0" class="cards-result-container" :class="{ 'many-cards': shouldAutoDraw }">
<div v-for="(card, index) in revealedCards" :key="index" class="card-result-wrapper" :class="`card-${index}`">
<div class="result-card" :class="{ flipped: isFlipped[index], 'auto-flipped': isAutoDrawing }" @click="flipCard(index)">
<div class="card-face card-unknown-front">
<div class="card-inner-content">
<img src="cards/1.png" alt="Card Back" class="card-back-image" />
@ -190,6 +285,7 @@ defineExpose({ setDrawnCards });
</div>
</div>
</transition>
<button
@click="goToSelection"
class="group mt-8 flex h-12 max-w-[480px] min-w-[200px] cursor-pointer items-center justify-center overflow-hidden rounded-full border border-[var(--c-purple)]/40 bg-gradient-to-r from-[var(--c-purple)]/20 to-[var(--c-purple)]/30 px-8 text-base font-bold tracking-wide text-[var(--c-white)] transition-all duration-300 hover:from-[var(--c-purple)]/40 hover:to-[var(--c-purple)]/50 hover:shadow-lg disabled:cursor-not-allowed disabled:opacity-60"
@ -221,6 +317,64 @@ defineExpose({ setDrawnCards });
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
padding: 2rem 1rem;
}
/* Auto-draw progress indicator */
.auto-draw-progress {
width: 100%;
max-width: 500px;
margin-bottom: 2rem;
text-align: center;
}
.progress-bar {
width: 100%;
height: 8px;
background: rgba(128, 90, 213, 0.2);
border-radius: 4px;
overflow: hidden;
margin-bottom: 0.5rem;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--c-purple), var(--c-gold));
border-radius: 4px;
transition: width 0.3s ease;
}
.progress-text {
font-size: 0.9rem;
color: var(--c-white);
opacity: 0.8;
}
/* Auto-draw prompt */
.auto-draw-prompt {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
z-index: 5;
background: rgba(0, 0, 0, 0.7);
padding: 1rem 1.5rem;
border-radius: 12px;
backdrop-filter: blur(10px);
border: 1px solid rgba(240, 186, 45, 0.3);
}
.prompt-icon {
font-size: 2rem;
margin-bottom: 0.5rem;
}
.prompt-text {
color: var(--c-white);
font-size: 0.9rem;
font-weight: 500;
}
.card {
@ -251,26 +405,42 @@ defineExpose({ setDrawnCards });
}
/* Hover sur la pile */
.card-stack:not(.spread):hover {
.card-stack:not(.spread):not(.auto-drawing):hover {
transform: translateY(-10px) rotateX(2deg) rotateY(-2deg);
}
.card-stack:not(.spread):hover .card:nth-child(1) {
.card-stack:not(.spread):not(.auto-drawing):hover .card:nth-child(1) {
transform: rotateY(-5deg) rotateX(5deg) translateZ(30px) translateX(-20px);
}
.card-stack:not(.spread):hover .card:nth-child(2) {
.card-stack:not(.spread):not(.auto-drawing):hover .card:nth-child(2) {
transform: rotateY(0deg) rotateX(2deg) translateZ(20px);
}
.card-stack:not(.spread):hover .card:nth-child(3) {
.card-stack:not(.spread):not(.auto-drawing):hover .card:nth-child(3) {
transform: rotateY(5deg) rotateX(5deg) translateZ(10px) translateX(20px);
}
/* Glow doré subtil au hover */
.card-stack:hover .card-back {
.card-stack:not(.auto-drawing):hover .card-back {
box-shadow: 0 0 20px rgba(215, 186, 141, 0.6);
transition: box-shadow 0.6s ease-in-out;
}
/* Auto-drawing state */
.card-stack.auto-drawing {
cursor: default;
animation: gentle-pulse 2s infinite;
}
@keyframes gentle-pulse {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.02);
}
}
/* Animation click */
@keyframes card-click-tilt {
0% {
@ -374,10 +544,15 @@ defineExpose({ setDrawnCards });
}
.card-result-wrapper {
width: 250px;
height: 400px;
width: clamp(140px, 12vw, 160px); /* target ~67 per row on wide screens */
aspect-ratio: 5 / 8; /* maintain 250x400 proportion */
perspective: 1000px;
border-radius: 18px;
transition: transform 0.5s ease;
}
.card-result-wrapper:hover {
transform: translateY(-10px);
}
.card-back-design-wrapper {
@ -429,7 +604,6 @@ defineExpose({ setDrawnCards });
display: flex;
justify-content: center;
align-items: center;
transform: rotateY(180deg);
position: relative;
border-radius: 18px;
@ -460,10 +634,24 @@ defineExpose({ setDrawnCards });
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 20px;
gap: 15px; /* tighter gaps to fit 67 per row */
margin-top: 2rem;
position: relative;
z-index: 10;
max-width: 100%;
padding: 0 1rem;
}
/* Enhanced layout for many cards */
.cards-result-container.many-cards {
gap: 15px;
justify-content: center; /* keep centered alignment even for many cards */
}
.cards-result-container.many-cards .card-result-wrapper {
width: clamp(140px, 12vw, 160px);
aspect-ratio: 5 / 8; /* keeps card proportions */
margin-bottom: 10px;
}
.card-result-container {
@ -483,6 +671,10 @@ defineExpose({ setDrawnCards });
transform: rotateY(180deg);
}
.result-card.auto-flipped {
transition: transform 0.8s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.card-front,
.card-back-info {
position: absolute;
@ -613,4 +805,143 @@ defineExpose({ setDrawnCards });
opacity: 0;
transform: translateY(50px);
}
/* Responsive design */
@media (max-width: 1200px) {
.cards-result-container .card-result-wrapper {
width: clamp(140px, 12vw, 160px);
/* height handled by aspect-ratio */
}
}
@media (max-width: 992px) {
.cards-result-container .card-result-wrapper {
width: clamp(140px, 20vw, 160px);
/* height handled by aspect-ratio */
}
.card {
width: 220px;
height: 352px;
}
.card-stack {
width: 280px;
height: 460px;
}
}
@media (max-width: 768px) {
.cards-result-container {
gap: 15px;
}
.cards-result-container .card-result-wrapper {
width: clamp(140px, 26vw, 160px);
/* height handled by aspect-ratio */
}
.card {
width: 200px;
height: 320px;
}
.card-stack {
width: 260px;
height: 420px;
}
.card-description-overlay h3 {
font-size: 1.4rem;
}
}
@media (max-width: 576px) {
.cards-result-container {
gap: 10px;
}
.cards-result-container .card-result-wrapper {
width: clamp(140px, 36vw, 160px);
/* height handled by aspect-ratio */
}
.card {
width: 180px;
height: 288px;
}
.card-stack {
width: 240px;
height: 380px;
}
.card-description-overlay h3 {
font-size: 1.2rem;
padding: 8px;
}
.auto-draw-prompt {
padding: 0.8rem 1rem;
}
.prompt-icon {
font-size: 1.5rem;
}
.prompt-text {
font-size: 0.8rem;
}
}
/* Animation for auto-drawing cards */
@keyframes card-appear {
0% {
opacity: 0;
transform: scale(0.8) translateY(20px);
}
100% {
opacity: 1;
transform: scale(1) translateY(0);
}
}
.cards-result-container .card-result-wrapper {
animation: card-appear 0.5s ease forwards;
}
/* Stagger animation for multiple cards */
.cards-result-container .card-result-wrapper:nth-child(1) {
animation-delay: 0.1s;
}
.cards-result-container .card-result-wrapper:nth-child(2) {
animation-delay: 0.2s;
}
.cards-result-container .card-result-wrapper:nth-child(3) {
animation-delay: 0.3s;
}
.cards-result-container .card-result-wrapper:nth-child(4) {
animation-delay: 0.4s;
}
.cards-result-container .card-result-wrapper:nth-child(5) {
animation-delay: 0.5s;
}
.cards-result-container .card-result-wrapper:nth-child(6) {
animation-delay: 0.6s;
}
.cards-result-container .card-result-wrapper:nth-child(7) {
animation-delay: 0.7s;
}
.cards-result-container .card-result-wrapper:nth-child(8) {
animation-delay: 0.8s;
}
.cards-result-container .card-result-wrapper:nth-child(9) {
animation-delay: 0.9s;
}
.cards-result-container .card-result-wrapper:nth-child(10) {
animation-delay: 1s;
}
.cards-result-container .card-result-wrapper:nth-child(n + 11) {
animation-delay: 1.1s;
}
</style>

View File

@ -1,17 +1,18 @@
<template>
<main class="flex flex-1 flex-col items-center justify-center px-4 pt-12 pb-24 sm:px-6 lg:px-8">
<div class="relative z-10 flex w-full max-w-4xl flex-col items-center justify-center text-center">
<main class="relative flex min-h-screen w-full flex-col px-4 pt-12 pb-24 sm:px-6 lg:px-8">
<!-- Header / intro kept centered and constrained -->
<div class="relative z-10 mx-auto max-w-4xl text-center">
<h1 class="text-5xl font-bold text-[var(--midnight-blue)] md:text-6xl">
L'Oracle de votre<span class="citadel-script ml-4 text-6xl text-[var(--spiritual-earth)] md:text-7xl">Destinée</span>
</h1>
<p class="mt-4 max-w-2xl text-lg text-[var(--midnight-blue)]/80">
<p class="mt-4 mx-auto max-w-2xl text-lg text-[var(--midnight-blue)]/80">
Puisez dans la sagesse ancestrale pour éclairer votre chemin. Tirez une carte et recevez le message qui vous est destiné aujourd'hui.
</p>
</div>
<!-- Card shuffle slot -->
<!-- Card shuffle slot now full width -->
<div class="relative z-10 mt-8 w-full">
<slot name="card-shuffle-slot" />
<!-- Button tirage -->
</div>
</main>
</template>

View File

@ -0,0 +1,81 @@
<template>
<article
class="group relative flex h-full flex-col overflow-hidden rounded-2xl border border-[var(--color-border)]/40 bg-[var(--card)]/95 shadow-sm transition-transform duration-300 hover:-translate-y-0.5 hover:shadow-xl"
>
<!-- Header / image -->
<div class="flex items-start gap-4 p-4 sm:p-5">
<div class="relative shrink-0">
<div
class="h-40 w-28 rounded-xl bg-cover bg-center bg-no-repeat shadow-md ring-1 ring-[var(--color-border)]/50"
:style="{
backgroundImage: `url(${resolvedImageUrl})`,
transform: orientation === 'reversed' ? 'rotate(180deg)' : 'none'
}"
></div>
<div
class="absolute -bottom-2 -left-2 flex h-8 w-8 items-center justify-center rounded-full border-2 border-[var(--color-border)]/60 bg-[var(--secondary)] text-xs font-black text-[var(--foreground)] shadow"
:title="`Carte #${cardNumber}`"
>
{{ cardNumber }}
</div>
<div
v-if="orientation"
class="absolute top-2 right-2 rounded-full px-2 py-0.5 text-[10px] font-bold tracking-wide text-white shadow"
:class="orientation === 'reversed' ? 'bg-red-600/90' : 'bg-[var(--primary)]'"
>
{{ orientation === 'reversed' ? 'Inversée' : 'Droite' }}
</div>
</div>
<div class="min-w-0 flex-1">
<h3 class="font-title text-lg font-black leading-snug text-[var(--foreground)]">
{{ name }}
</h3>
<p class="mt-1 text-xs uppercase tracking-wider text-[var(--muted-foreground)]">
Carte #{{ cardNumber }}
</p>
</div>
</div>
<!-- Description -->
<div class="relative px-4 pb-4 sm:px-5 sm:pb-5">
<div :class="[ collapsed ? 'max-h-40 overflow-hidden' : 'max-h-none', 'text-sm leading-relaxed text-[var(--foreground)]/85']">
<div v-html="description"></div>
</div>
<!-- Fade overlay when collapsed -->
<div
v-if="collapsed"
class="pointer-events-none absolute inset-x-0 bottom-12 h-16 bg-gradient-to-t from-[var(--card)] to-transparent"
></div>
<div class="mt-3 flex justify-end">
<button
type="button"
@click="collapsed = !collapsed"
class="rounded-full border border-[var(--color-border)]/60 bg-[var(--secondary)]/60 px-3 py-1 text-xs font-semibold text-[var(--foreground)] transition-colors hover:bg-[var(--secondary)]"
>
{{ collapsed ? 'Lire plus' : 'Réduire' }}
</button>
</div>
</div>
</article>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
interface Props {
cardNumber: number
name: string
imageUrl?: string
orientation?: string
description: string
}
const props = defineProps<Props>()
const resolvedImageUrl = computed(() => props.imageUrl || `/cards/${props.cardNumber + 1}.png`)
const collapsed = ref(true)
</script>

View File

@ -0,0 +1,81 @@
<script setup lang="ts">
import FullCardItem from '@/components/ui/card/FullCardItem.vue'
import LandingLayout from '@/layouts/app/LandingLayout.vue'
import axios from 'axios'
import { computed, onMounted, ref } from 'vue'
const cards = ref<Array<any>>([])
const error = ref<string | null>(null)
const loading = ref(true)
const params = new URLSearchParams(window.location.search)
const clientSessionId = params.get('client_session_id')
const hasCards = computed(() => cards.value.length > 0)
onMounted(async () => {
try {
if (!clientSessionId) {
error.value = 'No session ID provided. Please return to the payment page.'
return
}
const response = await axios.get(`/api/get-cards?client_session_id=${clientSessionId}`)
if (response.data.success) {
cards.value = response.data.cards
} else {
error.value = response.data.message || 'An error occurred while validating payment.'
}
} catch (err: any) {
console.error('Card fetch error:', err)
error.value = err.response?.data?.message || 'Failed to get cards from the server. Please contact support.'
} finally {
loading.value = false
}
})
</script>
<template>
<LandingLayout>
<main class="min-h-screen bg-gradient-to-br from-[var(--background)] to-[var(--background)]/95 py-8">
<div class="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<!-- Title / meta -->
<div class="mb-10 text-center md:mb-14">
<h1 class="font-title bg-gradient-to-r from-[var(--c-purple)] to-[var(--c-purple)]/70 bg-clip-text text-3xl font-black text-transparent sm:text-4xl md:text-5xl">
Lecture complète
</h1>
<p class="mt-2 text-[var(--muted-foreground)]">
{{ hasCards ? `${cards.length} cartes révélées` : '' }}
</p>
</div>
<!-- Loading -->
<div v-if="loading" class="flex items-center justify-center py-24">
<div class="h-12 w-12 animate-spin rounded-full border-2 border-[var(--c-purple)]/30 border-t-[var(--c-purple)]"></div>
</div>
<!-- Error -->
<div v-else-if="error" class="mx-auto max-w-2xl rounded-2xl border border-red-500/30 bg-red-900/20 p-6 text-center">
<p class="font-semibold text-red-200">{{ error }}</p>
</div>
<!-- Cards Grid -->
<div v-else-if="hasCards" class="grid justify-items-center gap-5 grid-cols-[repeat(auto-fit,minmax(160px,1fr))] sm:gap-6">
<FullCardItem
v-for="(card, index) in cards"
:key="card.id || index"
:card-number="card.id"
:name="card.name"
:image-url="card.image_url"
:orientation="card.orientation"
:description="card.description"
/>
</div>
<!-- Empty -->
<div v-else class="mx-auto max-w-2xl rounded-2xl border border-[var(--color-border)]/40 bg-[var(--card)]/80 p-10 text-center">
<p class="text-[var(--muted-foreground)]">Aucune carte trouvée</p>
</div>
</div>
</main>
</LandingLayout>
</template>

View File

@ -254,7 +254,7 @@ const clearHover = () => {
</svg>
</div>
</div>
<h3 class="text-xl font-bold text-white md:text-2xl">Quadrige Doré</h3>
<h3 class="text-xl font-bold text-white md:text-2xl">Tirage complet</h3>
<p class="mt-2 text-4xl font-bold text-[var(--c-gold)] md:text-5xl">99</p>
<p class="mt-4 text-sm text-gray-300 md:text-base">
Une lecture complète sur tous les aspects de votre vie : amour, carrière, spiritualité et abondance.
@ -262,7 +262,7 @@ const clearHover = () => {
</div>
<button
class="group relative mt-2 flex h-12 w-full items-center justify-center overflow-hidden rounded-full bg-gradient-to-r from-gray-800 to-gray-900 px-6 font-bold tracking-wide text-white transition-all duration-300 hover:from-[var(--c-purple)] hover:to-[var(--c-purple)]/80 hover:text-white"
@click="handleSelection(4)"
@click="handleSelection(21)"
>
<span class="relative z-10">Explorer</span>
<div