Spaces:
				
			
			
	
			
			
		Sleeping
		
	
	
	
			
			
	
	
	
	
		
		
		Sleeping
		
	| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>AI Flashcards Generator</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.6.2/axios.min.js"></script> | |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" rel="stylesheet"> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap'); | |
| body { | |
| font-family: 'Space Grotesk', sans-serif; | |
| background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); | |
| } | |
| .glass-morph { | |
| background: rgba(255, 255, 255, 0.05); | |
| backdrop-filter: blur(10px); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| } | |
| .card-hover { | |
| transition: all 0.3s ease; | |
| } | |
| .card-hover:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2); | |
| } | |
| .gradient-text { | |
| background: linear-gradient(45deg, #60a5fa, #a855f7); | |
| -webkit-background-clip: text; | |
| background-clip: text; | |
| color: transparent; | |
| } | |
| .custom-loader { | |
| width: 50px; | |
| height: 50px; | |
| border: 3px solid #fff; | |
| border-radius: 50%; | |
| display: inline-block; | |
| position: relative; | |
| box-sizing: border-box; | |
| animation: rotation 1s linear infinite; | |
| } | |
| .custom-loader::after { | |
| content: ''; | |
| box-sizing: border-box; | |
| position: absolute; | |
| left: 50%; | |
| top: 50%; | |
| transform: translate(-50%, -50%); | |
| width: 40px; | |
| height: 40px; | |
| border-radius: 50%; | |
| border: 3px solid transparent; | |
| border-bottom-color: #60a5fa; | |
| } | |
| @keyframes rotation { | |
| 0% { transform: rotate(0deg) } | |
| 100% { transform: rotate(360deg) } | |
| } | |
| .fade-enter-active, .fade-leave-active { | |
| transition: opacity 0.5s ease; | |
| } | |
| .fade-enter-from, .fade-leave-to { | |
| opacity: 0; | |
| } | |
| .flip-card { | |
| perspective: 1000px; | |
| height: 300px; | |
| } | |
| .flip-card-inner { | |
| position: relative; | |
| width: 100%; | |
| height: 100%; | |
| text-align: center; | |
| transition: transform 0.8s; | |
| transform-style: preserve-3d; | |
| cursor: pointer; | |
| } | |
| .flip-card.flipped .flip-card-inner { | |
| transform: rotateY(180deg); | |
| } | |
| .flip-card-front, .flip-card-back { | |
| position: absolute; | |
| width: 100%; | |
| height: 100%; | |
| -webkit-backface-visibility: hidden; | |
| backface-visibility: hidden; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 2rem; | |
| border-radius: 1rem; | |
| } | |
| .flip-card-front { | |
| background: rgba(255, 255, 255, 0.05); | |
| } | |
| .flip-card-back { | |
| background: rgba(255, 255, 255, 0.07); | |
| transform: rotateY(180deg); | |
| } | |
| .keyboard-shortcut { | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| min-width: 24px; | |
| height: 24px; | |
| padding: 0 6px; | |
| border-radius: 4px; | |
| background: rgba(255, 255, 255, 0.1); | |
| font-size: 0.875rem; | |
| margin: 0 2px; | |
| } | |
| .progress-bar { | |
| height: 4px; | |
| background: rgba(96, 165, 250, 0.2); | |
| border-radius: 2px; | |
| overflow: hidden; | |
| } | |
| .progress-value { | |
| height: 100%; | |
| background: linear-gradient(90deg, #60a5fa, #a855f7); | |
| transition: width 0.3s ease; | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen text-gray-100"> | |
| <div id="app" class="container mx-auto px-4 py-12"> | |
| <!-- Hero Section --> | |
| <div class="text-center mb-16 animate__animated animate__fadeIn"> | |
| <h1 class="text-5xl font-bold mb-4 gradient-text">AI Flashcards Generator</h1> | |
| <p class="text-xl text-gray-400 mb-8">Transformez vos sujets en cartes d'apprentissage intelligentes</p> | |
| </div> | |
| <!-- Main Content --> | |
| <div class="max-w-4xl mx-auto"> | |
| <!-- Input Section --> | |
| <div class="glass-morph rounded-2xl p-8 mb-12 card-hover"> | |
| <div class="mb-6"> | |
| <label for="topic" class="block text-lg font-medium mb-3 text-gray-300">Quel sujet souhaitez-vous explorer ?</label> | |
| <div class="relative"> | |
| <input | |
| type="text" | |
| id="topic" | |
| v-model="topic" | |
| @keyup.enter="generateFlashcards" | |
| class="w-full px-6 py-4 bg-gray-800/50 rounded-xl border border-gray-700 focus:ring-2 focus:ring-blue-500 focus:border-transparent text-lg transition-all duration-300" | |
| placeholder="Ex: Intelligence Artificielle, Quantum Computing..." | |
| :disabled="isLoading" | |
| > | |
| </div> | |
| </div> | |
| <button | |
| @click="generateFlashcards" | |
| :disabled="isLoading" | |
| class="w-full bg-gradient-to-r from-blue-500 to-purple-600 text-white py-4 px-8 rounded-xl font-medium text-lg hover:opacity-90 transition-all duration-300 flex items-center justify-center space-x-3" | |
| > | |
| <span v-if="!isLoading">Générer les Flashcards</span> | |
| <span v-else class="custom-loader"></span> | |
| </button> | |
| </div> | |
| <!-- Error Message --> | |
| <transition name="fade"> | |
| <div v-if="error" class="mb-8 animate__animated animate__shakeX"> | |
| <div class="bg-red-500/20 border border-red-500/50 text-red-300 px-6 py-4 rounded-xl"> | |
| [[error]] | |
| </div> | |
| </div> | |
| </transition> | |
| <!-- Results Section --> | |
| <transition name="fade"> | |
| <div v-if="flashcards.length > 0" class="glass-morph rounded-2xl overflow-hidden"> | |
| <!-- Progress Bar --> | |
| <div class="progress-bar"> | |
| <div class="progress-value" :style="{ width: `${(currentCardIndex + 1) * 100 / flashcards.length}%` }"></div> | |
| </div> | |
| <!-- Tabs --> | |
| <div class="border-b border-gray-700/50"> | |
| <div class="flex"> | |
| <button | |
| v-for="tab in ['study', 'json']" | |
| :key="tab" | |
| @click="activeTab = tab" | |
| :class="[ | |
| 'px-8 py-4 font-medium text-lg transition-all duration-300', | |
| activeTab === tab | |
| ? 'gradient-text border-b-2 border-blue-500' | |
| : 'text-gray-400 hover:text-gray-300' | |
| ]" | |
| > | |
| [[tab === 'study' ? 'Mode Étude' : 'Mode JSON']] | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Tab Content --> | |
| <div class="p-6"> | |
| <!-- Study Mode --> | |
| <div v-if="activeTab === 'study'" class="space-y-6"> | |
| <!-- Keyboard Shortcuts Info --> | |
| <div class="text-center mb-6 text-gray-400"> | |
| <span class="keyboard-shortcut">←</span> Précédent | |
| <span class="keyboard-shortcut mx-2">→</span> Suivant | |
| <span class="keyboard-shortcut">Espace</span> Retourner la carte | |
| </div> | |
| <div class="flip-card" :class="{ 'flipped': currentCard.showAnswer }" @click="toggleCard"> | |
| <div class="flip-card-inner"> | |
| <div class="flip-card-front"> | |
| <div class="text-center"> | |
| <div class="text-2xl font-medium mb-4">[[currentCard.question]]</div> | |
| <div class="text-gray-400 text-sm">(Cliquez pour voir la réponse)</div> | |
| </div> | |
| </div> | |
| <div class="flip-card-back"> | |
| <div class="text-center"> | |
| <div class="text-xl">[[currentCard.answer]]</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Navigation Controls --> | |
| <div class="flex items-center justify-center space-x-4 mt-8"> | |
| <button | |
| @click="previousCard" | |
| :disabled="currentCardIndex === 0" | |
| class="px-6 py-3 bg-blue-500/20 rounded-lg hover:bg-blue-500/30 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-300" | |
| > | |
| ← Précédent | |
| </button> | |
| <div class="text-lg font-medium"> | |
| [[currentCardIndex + 1]] / [[flashcards.length]] | |
| </div> | |
| <button | |
| @click="nextCard" | |
| :disabled="currentCardIndex === flashcards.length - 1" | |
| class="px-6 py-3 bg-blue-500/20 rounded-lg hover:bg-blue-500/30 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-300" | |
| > | |
| Suivant → | |
| </button> | |
| </div> | |
| </div> | |
| <!-- JSON Mode --> | |
| <div v-if="activeTab === 'json'" class="bg-gray-800/50 rounded-xl p-6 overflow-x-auto"> | |
| <pre class="text-gray-300">[[JSON.stringify(flashcards, null, 2)]]</pre> | |
| </div> | |
| </div> | |
| </div> | |
| </transition> | |
| </div> | |
| </div> | |
| <script> | |
| const { createApp } = Vue | |
| createApp({ | |
| delimiters: ['[[', ']]'], | |
| data() { | |
| return { | |
| topic: '', | |
| flashcards: [], | |
| activeTab: 'study', | |
| isLoading: false, | |
| error: null, | |
| currentCardIndex: 0 | |
| } | |
| }, | |
| computed: { | |
| currentCard() { | |
| return this.flashcards[this.currentCardIndex] || { | |
| question: '', | |
| answer: '', | |
| showAnswer: false | |
| } | |
| } | |
| }, | |
| methods: { | |
| async generateFlashcards() { | |
| if (!this.topic.trim()) { | |
| this.error = 'Veuillez entrer un sujet.' | |
| return | |
| } | |
| this.isLoading = true | |
| this.error = null | |
| this.flashcards = [] | |
| this.currentCardIndex = 0 | |
| try { | |
| const response = await axios.post('/generate', { | |
| topic: this.topic | |
| }) | |
| if (response.data.success) { | |
| const cards = response.data.flashcards.map(card => ({ | |
| ...card, | |
| showAnswer: false | |
| })) | |
| setTimeout(() => { | |
| this.flashcards = cards | |
| this.$nextTick(() => { | |
| gsap.from('.flip-card', { | |
| y: 30, | |
| opacity: 0, | |
| duration: 0.5 | |
| }) | |
| }) | |
| }, 300) | |
| } | |
| } catch (error) { | |
| this.error = error.response?.data?.error || 'Une erreur est survenue lors de la génération.' | |
| } finally { | |
| this.isLoading = false | |
| } | |
| }, | |
| nextCard() { | |
| if (this.currentCardIndex < this.flashcards.length - 1) { | |
| this.flashcards[this.currentCardIndex].showAnswer = false | |
| this.currentCardIndex++ | |
| this.animateCardTransition('next') | |
| } | |
| }, | |
| previousCard() { | |
| if (this.currentCardIndex > 0) { | |
| this.flashcards[this.currentCardIndex].showAnswer = false | |
| this.currentCardIndex-- | |
| this.animateCardTransition('prev') | |
| } | |
| }, | |
| toggleCard() { | |
| this.flashcards[this.currentCardIndex].showAnswer = !this.flashcards[this.currentCardIndex].showAnswer | |
| }, | |
| animateCardTransition(direction) { | |
| const xOffset = direction === 'next' ? -50 : 50 | |
| gsap.fromTo('.flip-card', | |
| { | |
| x: xOffset, | |
| opacity: 0 | |
| }, | |
| { | |
| x: 0, | |
| opacity: 1, | |
| duration: 0.3 | |
| } | |
| ) | |
| } | |
| }, | |
| mounted() { | |
| gsap.from('.gradient-text', { | |
| y: -50, | |
| opacity: 0, | |
| duration: 1, | |
| ease: 'power3.out' | |
| }) | |
| window.addEventListener('keydown', (e) => { | |
| if (this.activeTab === 'study') { | |
| if (e.key === 'ArrowRight') this.nextCard() | |
| if (e.key === 'ArrowLeft') this.previousCard() | |
| if (e.key === ' ') { | |
| e.preventDefault() | |
| this.toggleCard() | |
| } | |
| } | |
| }) | |
| } | |
| }).mount('#app') | |
| </script> | |
| </body> | |
| </html> | 
