multi carte
This commit is contained in:
parent
cf89535c14
commit
fa94499268
@ -30,7 +30,7 @@ class StripeController extends Controller
|
||||
|
||||
$priceIds = [
|
||||
3 => 'price_1S51zxGaZ3yeYkzWYb0wSt4j',
|
||||
4 => 'price_1S5464GaZ3yeYkzWh8RuJfab',
|
||||
21 => 'price_1S5464GaZ3yeYkzWh8RuJfab',
|
||||
];
|
||||
|
||||
if (!isset($priceIds[$count])) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@
|
||||
|
||||
<style scoped>
|
||||
.citadel-script {
|
||||
font-family: 'Dancing Script', cursive;
|
||||
font-family: var(--font-sub-title), cursive;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
|
||||
@ -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 ~6–7 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 6–7 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>
|
||||
|
||||
@ -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>
|
||||
|
||||
81
resources/js/components/ui/card/FullCardItem.vue
Normal file
81
resources/js/components/ui/card/FullCardItem.vue
Normal 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>
|
||||
0
resources/js/pages/cards/FullCardResult.vue
Normal file
0
resources/js/pages/cards/FullCardResult.vue
Normal file
81
resources/js/pages/cards/FullResult.vue
Normal file
81
resources/js/pages/cards/FullResult.vue
Normal 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>
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user