Mariam-cards / templates /index.html
Docfile's picture
Update templates/index.html
db14463 verified
raw
history blame
16 kB
<!DOCTYPE html>
<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>