Merge branch 'UI-Responsiveness' into 'dev'

Ui responsiveness

See merge request eb2342s/swe-b1-a!5
This commit was merged in pull request #65.
This commit is contained in:
Kelvi Yawo Jules Agbessi Awuklu
2024-11-17 17:40:57 +01:00
49 changed files with 603 additions and 193 deletions

View File

@@ -1,5 +1,5 @@
{ {
"displayName": "Thema 1", "displayName": "Geschichten erzählen",
"icon": "fa-divide", "icon": "fa-divide",
"description": "Eine kurze Beschreibung des Themas", "description": "Eine kurze Beschreibung des Themas",
"relatedTopics": [ "relatedTopics": [

View File

@@ -1,5 +1,5 @@
{ {
"displayName": "Thema 2", "displayName": "Wortarten",
"icon": "fa-divide", "icon": "fa-divide",
"description": "Eine kurze Beschreibung des Themas", "description": "Eine kurze Beschreibung des Themas",
"relatedTopics": [ "relatedTopics": [

View File

@@ -1,5 +1,5 @@
{ {
"displayName": "Thema 3", "displayName": "Vier Fälle",
"icon": "fa-divide", "icon": "fa-divide",
"description": "Eine kurze Beschreibung des Themas", "description": "Eine kurze Beschreibung des Themas",
"relatedTopics": [ "relatedTopics": [

View File

@@ -1,5 +1,5 @@
{ {
"displayName": "Thema 4", "displayName": "Personalpronomen",
"icon": "fa-divide", "icon": "fa-divide",
"description": "Eine kurze Beschreibung des Themas", "description": "Eine kurze Beschreibung des Themas",
"relatedTopics": [ "relatedTopics": [

View File

@@ -1,5 +1,5 @@
{ {
"displayName": "Thema 5", "displayName": "Personalpronomen in den vier Fällen",
"icon": "fa-divide", "icon": "fa-divide",
"description": "Eine kurze Beschreibung des Themas", "description": "Eine kurze Beschreibung des Themas",
"relatedTopics": [ "relatedTopics": [

View File

@@ -1,5 +1,5 @@
{ {
"displayName": "Thema 6", "displayName": "Satzglieder",
"icon": "fa-divide", "icon": "fa-divide",
"description": "Eine kurze Beschreibung des Themas", "description": "Eine kurze Beschreibung des Themas",
"relatedTopics": [ "relatedTopics": [

View File

@@ -1,5 +1,5 @@
{ {
"displayName": "Thema 7", "displayName": "Possessivpronomen",
"icon": "fa-divide", "icon": "fa-divide",
"description": "Eine kurze Beschreibung des Themas", "description": "Eine kurze Beschreibung des Themas",
"relatedTopics": [ "relatedTopics": [

View File

@@ -1,5 +1,5 @@
{ {
"displayName": "Thema 8", "displayName": "Adverbiale Bestimmung",
"icon": "fa-divide", "icon": "fa-divide",
"description": "Eine kurze Beschreibung des Themas", "description": "Eine kurze Beschreibung des Themas",
"relatedTopics": [ "relatedTopics": [

View File

@@ -1,5 +1,5 @@
{ {
"displayName": "Thema 1", "displayName": "Schriftliches Multiplizieren",
"icon": "fa-divide", "icon": "fa-divide",
"description": "Eine kurze Beschreibung des Themas", "description": "Eine kurze Beschreibung des Themas",
"relatedTopics": [ "relatedTopics": [

View File

@@ -0,0 +1,5 @@
Das ist der Erklärtext
<img alt="Ein Bild" src="$TOPICPATH/image.png">
<br>
When \(a \ne 0\), there are two solutions to \(ax^2 + bx + c = 0\) and they are
$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -0,0 +1,11 @@
{
"displayName": "Rechnen mit Zeit",
"icon": "fa-divide",
"description": "Eine kurze Beschreibung des Themas",
"relatedTopics": [
"topic2", "topic3"
],
"files": [
"exercise1.pdf"
]
}

View File

@@ -0,0 +1,5 @@
Das ist der Erklärtext
<img alt="Ein Bild" src="$TOPICPATH/image.png">
<br>
When \(a \ne 0\), there are two solutions to \(ax^2 + bx + c = 0\) and they are
$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -0,0 +1,11 @@
{
"displayName": "Punkt- vor Strichrechnung",
"icon": "fa-divide",
"description": "Eine kurze Beschreibung des Themas",
"relatedTopics": [
"topic2", "topic3"
],
"files": [
"exercise1.pdf"
]
}

View File

@@ -0,0 +1,5 @@
Das ist der Erklärtext
<img alt="Ein Bild" src="$TOPICPATH/image.png">
<br>
When \(a \ne 0\), there are two solutions to \(ax^2 + bx + c = 0\) and they are
$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -0,0 +1,11 @@
{
"displayName": "Rechnen mit Klammern",
"icon": "fa-divide",
"description": "Eine kurze Beschreibung des Themas",
"relatedTopics": [
"topic2", "topic3"
],
"files": [
"exercise1.pdf"
]
}

View File

@@ -1,5 +1,5 @@
{ {
"displayName": "Thema 2", "displayName": "Schriftliches Dividieren",
"icon": "fa-divide", "icon": "fa-divide",
"description": "Eine kurze Beschreibung des Themas", "description": "Eine kurze Beschreibung des Themas",
"relatedTopics": [ "relatedTopics": [

View File

@@ -1,5 +1,5 @@
{ {
"displayName": "Thema 3", "displayName": "Echte Brüche",
"icon": "fa-divide", "icon": "fa-divide",
"description": "Eine kurze Beschreibung des Themas", "description": "Eine kurze Beschreibung des Themas",
"relatedTopics": [ "relatedTopics": [

View File

@@ -0,0 +1,5 @@
Das ist der Erklärtext
<img alt="Ein Bild" src="$TOPICPATH/image.png">
<br>
When \(a \ne 0\), there are two solutions to \(ax^2 + bx + c = 0\) and they are
$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -0,0 +1,11 @@
{
"displayName": "Unechte Brüche",
"icon": "fa-divide",
"description": "Eine kurze Beschreibung des Themas",
"relatedTopics": [
"topic2", "topic3"
],
"files": [
"exercise1.pdf"
]
}

View File

@@ -0,0 +1,5 @@
Das ist der Erklärtext
<img alt="Ein Bild" src="$TOPICPATH/image.png">
<br>
When \(a \ne 0\), there are two solutions to \(ax^2 + bx + c = 0\) and they are
$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -0,0 +1,11 @@
{
"displayName": "Gemischte Zahlen",
"icon": "fa-divide",
"description": "Eine kurze Beschreibung des Themas",
"relatedTopics": [
"topic2", "topic3"
],
"files": [
"exercise1.pdf"
]
}

View File

@@ -0,0 +1,5 @@
Das ist der Erklärtext
<img alt="Ein Bild" src="$TOPICPATH/image.png">
<br>
When \(a \ne 0\), there are two solutions to \(ax^2 + bx + c = 0\) and they are
$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -0,0 +1,11 @@
{
"displayName": "Addition & Subtraktion mit Brüchen",
"icon": "fa-divide",
"description": "Eine kurze Beschreibung des Themas",
"relatedTopics": [
"topic2", "topic3"
],
"files": [
"exercise1.pdf"
]
}

View File

@@ -0,0 +1,5 @@
Das ist der Erklärtext
<img alt="Ein Bild" src="$TOPICPATH/image.png">
<br>
When \(a \ne 0\), there are two solutions to \(ax^2 + bx + c = 0\) and they are
$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -0,0 +1,11 @@
{
"displayName": "Multiplikation & Division mit Brüchen",
"icon": "fa-divide",
"description": "Eine kurze Beschreibung des Themas",
"relatedTopics": [
"topic2", "topic3"
],
"files": [
"exercise1.pdf"
]
}

View File

@@ -0,0 +1,5 @@
Das ist der Erklärtext
<img alt="Ein Bild" src="$TOPICPATH/image.png">
<br>
When \(a \ne 0\), there are two solutions to \(ax^2 + bx + c = 0\) and they are
$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -0,0 +1,11 @@
{
"displayName": "Rechnen mit Gewichten",
"icon": "fa-divide",
"description": "Eine kurze Beschreibung des Themas",
"relatedTopics": [
"topic2", "topic3"
],
"files": [
"exercise1.pdf"
]
}

View File

@@ -0,0 +1,5 @@
Das ist der Erklärtext
<img alt="Ein Bild" src="$TOPICPATH/image.png">
<br>
When \(a \ne 0\), there are two solutions to \(ax^2 + bx + c = 0\) and they are
$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -0,0 +1,11 @@
{
"displayName": "Rechnen mit Längen",
"icon": "fa-divide",
"description": "Eine kurze Beschreibung des Themas",
"relatedTopics": [
"topic2", "topic3"
],
"files": [
"exercise1.pdf"
]
}

View File

@@ -23,13 +23,13 @@
.card-hover:hover { .card-hover:hover {
transform: translateY(-10px) scale(1.02); transform: translateY(-10px) scale(1.02);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1),
0 10px 10px -5px rgba(0, 0, 0, 0.04); 0 10px 10px -5px rgba(0, 0, 0, 0.04);
} }
.gradient-border { .gradient-border {
position: relative; position: relative;
background: linear-gradient(white, white) padding-box, background: linear-gradient(white, white) padding-box,
linear-gradient(45deg, #7C3AED, #F59E0B) border-box; linear-gradient(45deg, #7C3AED, #F59E0B) border-box;
border: 4px solid transparent; border: 4px solid transparent;
border-radius: 1rem; border-radius: 1rem;
} }
@@ -97,88 +97,143 @@
</head> </head>
<body class="bg-gray-50"> <body class="bg-gray-50">
<!-- Animated background blobs --> <!-- Animated background blobs -->
<div class="fixed inset-0 -z-10 overflow-hidden"> <div class="fixed inset-0 -z-10 overflow-hidden">
<div class="blob absolute w-96 h-96 bg-purple-300/30 -top-48 -left-16"></div> <div class="blob absolute w-96 h-96 bg-purple-300/30 -top-48 -left-16"></div>
<div class="blob absolute w-96 h-96 bg-amber-300/30 bottom-0 right-0"></div> <div class="blob absolute w-96 h-96 bg-amber-300/30 bottom-0 right-0"></div>
</div> </div>
<!-- Navigation -->
<nav class="fixed top-0 w-full bg-white/80 backdrop-blur-lg shadow-sm z-50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex items-center">
<span class="text-2xl font-bold text-purple-600">Lern<span class="text-amber-500">Portal</span></span>
</div>
<!-- Add this accessibility controls -->
<!-- Navigation -->
<nav class="fixed top-0 w-full bg-white/80 backdrop-blur-lg shadow-sm z-50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex items-center">
<span class="text-2xl font-bold text-purple-600">Lern<span class="text-amber-500">Portal</span></span>
</div> </div>
</div> <!-- Add this accessibility controls -->
</nav>
<!-- Hero Section -->
<div class="pt-24 pb-16 px-4">
<div class="max-w-7xl mx-auto">
<div class="text-center">
<h1 class="text-5xl md:text-6xl font-bold text-gray-900 mb-6 pulse">
Entdecke deine <span class="text-purple-600">Lernreise</span>
</h1>
<p class="text-xl text-gray-600 max-w-2xl mx-auto">
Interaktives Lernen für die digitale Generation. Personalisierte Lernpfade, sofortiges Feedback und spielerische Elemente.
</p>
</div>
</div> </div>
</div> </div>
</nav>
<!-- Subject Grid --> <!-- Hero Section -->
<div class="max-w-7xl mx-auto px-4 py-12 grid grid-cols-1 md:grid-cols-2 gap-8 mb-8"> <div class="pt-24 pb-16 px-4">
<!-- Mathematik --> <div class="max-w-7xl mx-auto">
<?php <div class="text-center">
require_once ("classes/SubjectData.php"); <h1 class="text-5xl md:text-6xl font-bold text-gray-900 mb-6 pulse">
$subjects = SubjectData::getAll(); Entdecke deine <span class="text-purple-600">Lernreise</span>
</h1>
<p class="text-xl text-gray-600 max-w-2xl mx-auto">
Interaktives Lernen für die digitale Generation. Personalisierte Lernpfade, sofortiges Feedback und spielerische Elemente.
</p>
</div>
</div>
</div>
foreach ($subjects as $subject) { <!-- Subject Grid -->
?> <div class="max-w-7xl mx-auto px-4 py-12 grid grid-cols-1 md:grid-cols-2 gap-8 mb-8">
<a href="subject.php?subject=<?php echo($subject->id); ?>" class="block"> <!-- Mathematik -->
<div class="gradient-border p-6 card-hover bg-white"> <?php
<div class="flex items-start space-x-4"> require_once ("classes/SubjectData.php");
<div class="w-12 h-12 rounded-lg bg-purple-100 flex items-center justify-center"> $subjects = SubjectData::getAll();
<i class="fas <?php echo($subject->icon); ?> text-2xl text-purple-600"></i>
</div> // Function to generate color variants from base color
<div class="flex-1"> function generateColorVariants($baseColor) {
<h3 class="text-xl font-bold text-gray-900 mb-2"><?php echo($subject->displayName); ?></h3> // Remove # if present
<p class="text-gray-600 mb-4"><?php echo($subject->description); ?></p> $baseColor = ltrim($baseColor, '#');
<div class="grid grid-cols-3 gap-4 mb-4">
<div class="text-center p-2 rounded-lg bg-purple-50"> // Convert to RGB
<div class="font-bold text-purple-600">150+</div> $r = hexdec(substr($baseColor, 0, 2));
<div class="text-sm text-gray-600">Übungen</div> $g = hexdec(substr($baseColor, 2, 2));
</div> $b = hexdec(substr($baseColor, 4, 2));
<div class="text-center p-2 rounded-lg bg-purple-50">
<div class="font-bold text-purple-600">12</div> // Generate lighter variant for secondary
<div class="text-sm text-gray-600">Kapitel</div> $secondary = sprintf("#%02x%02x%02x",
</div> min(255, $r + 70),
<div class="text-center p-2 rounded-lg bg-purple-50"> min(255, $g + 70),
<div class="font-bold text-purple-600">4.8</div> min(255, $b + 70)
<div class="text-sm text-gray-600">Bewertung</div> );
</div>
</div> // Generate very light variant for background
<div class="flex items-center space-x-2"> $light = sprintf("#%02x%02x%02x",
<span class="text-sm text-gray-500">Aktuelle Einheit:</span> min(255, $r + 140),
<span class="px-2 py-1 rounded-full bg-purple-100 text-purple-600 text-sm">Bruchrechnung</span> min(255, $g + 140),
</div> min(255, $b + 140)
</div> );
return [
'primary' => '#' . $baseColor,
'secondary' => $secondary,
'light' => $light
];
}
// Generate colors for each subject
foreach ($subjects as $subject) {
$colors = generateColorVariants($subject->color);
?>
<a href="subject.php?subject=<?php echo($subject->id); ?>" class="block">
<div class="gradient-border p-6 card-hover" style="
background: linear-gradient(145deg,
<?php echo $colors['light']; ?> 0%,
white 100%
);
border: 2px solid <?php echo $colors['secondary']; ?>;
border-radius: 1rem;
box-shadow: 0 4px 6px -1px <?php echo $colors['light']; ?>,
0 2px 4px -2px <?php echo $colors['secondary']; ?>;">
<div class="flex items-start space-x-4">
<div class="w-12 h-12 rounded-lg flex items-center justify-center"
style="background-color: rgba(<?php
$hex = ltrim($colors['primary'], '#');
$r = hexdec(substr($hex, 0, 2));
$g = hexdec(substr($hex, 2, 2));
$b = hexdec(substr($hex, 4, 2));
echo "$r, $g, $b, 0.1";
?>)">
<i class="fas <?php echo($subject->icon); ?> text-2xl"
style="color: <?php echo $colors['primary']; ?>"></i>
</div>
<div class="flex-1">
<h3 class="text-xl font-bold text-gray-900 mb-2"><?php echo($subject->displayName); ?></h3>
<p class="text-gray-600 mb-4"><?php echo($subject->description); ?></p>
<div class="grid grid-cols-3 gap-4 mb-4">
<div class="text-center p-2 rounded-lg"
style="background-color: <?php echo $colors['light']; ?>">
<div class="font-bold"
style="color: <?php echo $colors['primary']; ?>">150+</div>
<div class="text-sm text-gray-600">Übungen</div>
</div>
<div class="text-center p-2 rounded-lg bg-purple-50">
<div class="font-bold text-purple-600">12</div>
<div class="text-sm text-gray-600">Kapitel</div>
</div>
<div class="text-center p-2 rounded-lg bg-purple-50">
<div class="font-bold text-purple-600">4.8</div>
<div class="text-sm text-gray-600">Bewertung</div>
</div> </div>
</div> </div>
</a> <div class="flex items-center space-x-2">
<span class="text-sm text-gray-500">Aktuelle Einheit:</span>
<span class="px-2 py-1 rounded-full text-sm"
style="background-color: <?php echo $colors['light']; ?>;
color: <?php echo $colors['primary']; ?>">
Bruchrechnung
</span>
</div>
</div>
</div>
</div>
</a>
<?php <?php
} }
?> ?>
</div> </div>
<button class="scroll-top" onclick="scrollToTop()" id="scrollTop"> <button class="scroll-top" onclick="scrollToTop()" id="scrollTop">
<i class="fas fa-arrow-up"></i> <i class="fas fa-arrow-up"></i>
</button> </button>
<footer class="fixed bottom-0 w-full bg-white/80 backdrop-blur-lg shadow-sm p-5"> <footer class="fixed bottom-0 w-full bg-white/80 backdrop-blur-lg shadow-sm p-5">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
@@ -189,65 +244,65 @@
</div> </div>
</footer> </footer>
<script> <script>
// Initialize GSAP animations // Initialize GSAP animations
gsap.from(".gradient-border", { gsap.from(".gradient-border", {
duration: 0.8, duration: 0.8,
y: 60, y: 60,
opacity: 0, opacity: 0,
stagger: 0.2, stagger: 0.2,
ease: "power3.out" ease: "power3.out"
}); });
// Add intersection observer for scroll animations // Add intersection observer for scroll animations
const observer = new IntersectionObserver((entries) => { const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => { entries.forEach(entry => {
if (entry.isIntersecting) { if (entry.isIntersecting) {
entry.target.style.transform = "translateY(0)"; entry.target.style.transform = "translateY(0)";
entry.target.style.opacity = "1"; entry.target.style.opacity = "1";
}
});
}, { threshold: 0.1 });
document.querySelectorAll('.gradient-border').forEach((el) => {
observer.observe(el);
});
// Scroll to top functionality
window.onscroll = function() {
const scrollBtn = document.getElementById('scrollTop');
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
scrollBtn.classList.add('visible');
} else {
scrollBtn.classList.remove('visible');
} }
}; });
}, { threshold: 0.1 });
function scrollToTop() { document.querySelectorAll('.gradient-border').forEach((el) => {
window.scrollTo({ observer.observe(el);
top: 0, });
behavior: 'smooth'
}); // Scroll to top functionality
window.onscroll = function() {
const scrollBtn = document.getElementById('scrollTop');
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
scrollBtn.classList.add('visible');
} else {
scrollBtn.classList.remove('visible');
} }
};
// Add hover effects for subject cards function scrollToTop() {
document.querySelectorAll('.gradient-border').forEach(card => { window.scrollTo({
card.addEventListener('mouseenter', () => { top: 0,
gsap.to(card.querySelector('.w-12'), { behavior: 'smooth'
rotate: -10, });
scale: 1.2, }
duration: 0.3
});
});
card.addEventListener('mouseleave', () => { // Add hover effects for subject cards
gsap.to(card.querySelector('.w-12'), { document.querySelectorAll('.gradient-border').forEach(card => {
rotate: 0, card.addEventListener('mouseenter', () => {
scale: 1, gsap.to(card.querySelector('.w-12'), {
duration: 0.3 rotate: -10,
}); scale: 1.2,
duration: 0.3
}); });
}); });
</script>
card.addEventListener('mouseleave', () => {
gsap.to(card.querySelector('.w-12'), {
rotate: 0,
scale: 1,
duration: 0.3
});
});
});
</script>
</body> </body>
</html> </html>

View File

@@ -109,16 +109,16 @@ $topics = $subjectData->topics;
.search-container { .search-container {
position: fixed; position: fixed;
top: 0; top: 0;
left: 280px;
right: 0; right: 0;
padding: 1rem 2rem; padding: 1rem;
background: var(--card-bg); background: var(--card-bg);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
transition: left 0.3s ease;
z-index: 40; z-index: 40;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 1rem; gap: 1rem;
transition: all 0.3s ease;
width: auto;
} }
.search-box { .search-box {
@@ -146,11 +146,12 @@ $topics = $subjectData->topics;
.container { .container {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); /* Back to original 380px for desktop */
gap: 2rem; gap: 2rem; /* Back to original 2rem gap */
padding: 2rem; padding: 2rem;
max-width: 1600px; max-width: 1600px;
margin: 0 auto; margin: 0 auto;
width: 100%;
} }
.topic-card { .topic-card {
@@ -296,7 +297,31 @@ $topics = $subjectData->topics;
transition: width 1.5s cubic-bezier(0.4, 0, 0.2, 1); transition: width 1.5s cubic-bezier(0.4, 0, 0.2, 1);
} }
@media (min-width: 1025px) {
.menu-toggle {
display: none;
}
.sidebar {
transform: translateX(0);
}
.search-container {
left: 280px; /* Sidebar width */
width: calc(100% - 280px);
}
.main-content {
margin-left: 280px;
width: calc(100% - 280px);
}
}
@media (max-width: 1024px) { @media (max-width: 1024px) {
.menu-toggle {
display: flex !important;
}
.sidebar { .sidebar {
transform: translateX(-100%); transform: translateX(-100%);
} }
@@ -305,13 +330,58 @@ $topics = $subjectData->topics;
transform: translateX(0); transform: translateX(0);
} }
.search-container {
left: 0;
width: 100%;
padding-left: 4.5rem;
}
.main-content { .main-content {
margin-left: 0; margin-left: 0;
width: 100%; width: 100%;
} }
.search-box {
width: calc(100% - 2rem);
}
}
@media (max-width: 768px) {
.search-container { .search-container {
left: 0; padding: 1rem;
padding-left: 4.5rem;
}
.search-box {
width: 100%;
}
}
@media (max-width: 480px) {
.search-container {
padding: 0.75rem;
padding-left: 4rem; /* Keep space for menu icon */
}
.search-box {
font-size: 0.9rem;
padding: 0.5rem 0.75rem;
}
.download-links {
flex-direction: column;
}
.download-btn {
width: 100%;
}
.container {
padding: 0.5rem;
}
.topic-card {
margin: 0 0.5rem;
} }
} }
@@ -320,20 +390,21 @@ $topics = $subjectData->topics;
top: 1rem; top: 1rem;
left: 1rem; left: 1rem;
z-index: 60; z-index: 60;
background: white; background: var(--card-bg);
border: none; border: none;
padding: 0.75rem; padding: 0.75rem;
border-radius: 10px; border-radius: 10px;
cursor: pointer; cursor: pointer;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
display: none; width: 42px;
height: 42px;
display: none; /* Hidden by default */
align-items: center;
justify-content: center;
color: var(--text-primary);
transition: all 0.3s ease;
} }
@media (max-width: 1024px) {
.menu-toggle {
display: block;
}
}
/* Add floating animation for icons */ /* Add floating animation for icons */
@keyframes float { @keyframes float {
0%, 100% { transform: translateY(0); } 0%, 100% { transform: translateY(0); }
@@ -523,15 +594,6 @@ $topics = $subjectData->topics;
<a href="#" class="nav-link"> <a href="#" class="nav-link">
<i class="fas fa-book"></i> Kurse <i class="fas fa-book"></i> Kurse
</a> </a>
<a href="#" class="nav-link">
<i class="fas fa-trophy"></i> Erfolge
</a>
<a href="#" class="nav-link">
<i class="fas fa-chart-line"></i> Fortschritt
</a>
<a href="#" class="nav-link">
<i class="fas fa-question-circle"></i> Hilfe
</a>
</nav> </nav>
<div class="search-container"> <div class="search-container">
@@ -559,13 +621,13 @@ $topics = $subjectData->topics;
<h4>Verwandte Themen:</h4> <h4>Verwandte Themen:</h4>
<ul> <ul>
<?php <?php
foreach ($topicData->relatedTopics as $relatedTopic) { foreach ($topicData->relatedTopics as $relatedTopic) {
?> ?>
<li><?php echo($relatedTopic); ?></li> <li><?php echo($relatedTopic); ?></li>
<?php <?php
} }
?> ?>
</ul> </ul>
</div> </div>
@@ -574,17 +636,17 @@ $topics = $subjectData->topics;
<h4>Übungen herunterladen:</h4> <h4>Übungen herunterladen:</h4>
<div class="download-links"> <div class="download-links">
<?php <?php
foreach ($topicData->files as $fileName) { foreach ($topicData->files as $fileName) {
?> ?>
<a href="<?php echo("config/subjects/$subjectData->id/topics/$topicData->id/$fileName")?>" target="_blank" download class="download-btn"> <a href="<?php echo("config/subjects/$subjectData->id/topics/$topicData->id/$fileName")?>" target="_blank" download class="download-btn">
<i class="fas fa-file-pdf"></i> <i class="fas fa-file-pdf"></i>
<?php echo($fileName); ?> <?php echo($fileName); ?>
</a> </a>
<?php <?php
} }
?> ?>
</div> </div>
</div> </div>
@@ -601,10 +663,101 @@ $topics = $subjectData->topics;
</div> </div>
<script> <script>
function toggleSidebar() { document.addEventListener('DOMContentLoaded', () => {
document.querySelector('.sidebar').classList.toggle('active'); const menuToggle = document.querySelector('.menu-toggle');
const sidebar = document.querySelector('.sidebar');
const searchContainer = document.querySelector('.search-container');
const mainContent = document.querySelector('.main-content');
// Function to handle sidebar toggle
function toggleSidebar() {
sidebar.classList.toggle('active');
// Add overlay when sidebar is active on mobile/tablet
if (window.innerWidth <= 1024) {
if (sidebar.classList.contains('active')) {
addOverlay();
} else {
removeOverlay();
}
}
}
// Function to add overlay
function addOverlay() {
const overlay = document.createElement('div');
overlay.className = 'sidebar-overlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 45;
transition: opacity 0.3s ease;
`;
document.body.appendChild(overlay);
// Close sidebar when clicking overlay
overlay.addEventListener('click', () => {
sidebar.classList.remove('active');
removeOverlay();
});
// Fade in
requestAnimationFrame(() => {
overlay.style.opacity = '1';
});
}
// Function to remove overlay
function removeOverlay() {
const overlay = document.querySelector('.sidebar-overlay');
if (overlay) {
overlay.style.opacity = '0';
setTimeout(() => overlay.remove(), 300);
}
}
// Add click event to menu toggle
menuToggle.addEventListener('click', toggleSidebar);
// Handle window resize
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
if (window.innerWidth > 1024) {
sidebar.classList.remove('active');
removeOverlay();
}
}, 250);
});
// Handle escape key to close sidebar
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && sidebar.classList.contains('active')) {
sidebar.classList.remove('active');
removeOverlay();
}
});
});
// Add this right after your existing toggleSidebar function
function updateMenuVisibility() {
const menuToggle = document.querySelector('.menu-toggle');
if (window.innerWidth <= 768) { // Smartphone breakpoint
menuToggle.style.display = 'flex';
} else {
menuToggle.style.display = 'none';
}
} }
// Add event listeners
window.addEventListener('load', updateMenuVisibility);
window.addEventListener('resize', updateMenuVisibility);
// Animate progress bars on load // Animate progress bars on load
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const progressBars = document.querySelectorAll('.progress-bar'); const progressBars = document.querySelectorAll('.progress-bar');
@@ -671,9 +824,11 @@ $topics = $subjectData->topics;
const progressBars = document.querySelectorAll('.progress-bar'); const progressBars = document.querySelectorAll('.progress-bar');
progressBars.forEach(bar => { progressBars.forEach(bar => {
const progressElement = bar.querySelector('.progress'); const progressElement = bar.querySelector('.progress');
setTimeout(() => { if (progressElement) { // Add null check
progressElement.style.width = '100%'; setTimeout(() => {
}, 300); progressElement.style.width = '100%';
}, 300);
}
}); });
}); });
@@ -714,29 +869,42 @@ $topics = $subjectData->topics;
// Add card hover effects // Add card hover effects
document.querySelectorAll('.topic-card').forEach(card => { document.querySelectorAll('.topic-card').forEach(card => {
card.addEventListener('mouseenter', () => { card.addEventListener('mouseenter', () => {
gsap.to(card.querySelector('.topic-icon'), { const icon = card.querySelector('.topic-icon');
rotate: -10, if (window.gsap) {
scale: 1.2, gsap.to(icon, {
duration: 0.3 rotate: -10,
}); scale: 1.2,
duration: 0.3
});
} else {
// Fallback animation using CSS
icon.style.transform = 'rotate(-10deg) scale(1.2)';
}
}); });
card.addEventListener('mouseleave', () => { card.addEventListener('mouseleave', () => {
gsap.to(card.querySelector('.topic-icon'), { const icon = card.querySelector('.topic-icon');
rotate: 0, if (window.gsap) {
scale: 1, gsap.to(icon, {
duration: 0.3 rotate: 0,
}); scale: 1,
duration: 0.3
});
} else {
// Fallback animation using CSS
icon.style.transform = 'none';
}
}); });
}); });
// Update search function with fallback animation
function handleSearch() { function handleSearch() {
const searchTerm = document.getElementById('searchInput').value.toLowerCase(); const searchTerm = document.getElementById('searchInput').value.toLowerCase();
const topicCards = document.querySelectorAll('.topic-card'); const topicCards = document.querySelectorAll('.topic-card');
topicCards.forEach(card => { topicCards.forEach(card => {
const title = card.querySelector('.topic-title').textContent.toLowerCase(); const title = card.querySelector('.topic-title')?.textContent.toLowerCase() || '';
const description = card.querySelector('.topic-description').textContent.toLowerCase(); const description = card.querySelector('.topic-description')?.textContent.toLowerCase() || '';
const relatedTopics = Array.from(card.querySelectorAll('.related-topics li')) const relatedTopics = Array.from(card.querySelectorAll('.related-topics li'))
.map(li => li.textContent.toLowerCase()) .map(li => li.textContent.toLowerCase())
.join(' '); .join(' ');
@@ -745,17 +913,60 @@ $topics = $subjectData->topics;
if (content.includes(searchTerm)) { if (content.includes(searchTerm)) {
card.style.display = 'block'; card.style.display = 'block';
// Optional: Add animation for appearing cards if (window.gsap) {
gsap.to(card, { gsap.to(card, {
opacity: 1, opacity: 1,
y: 0, y: 0,
duration: 0.3 duration: 0.3
}); });
} else {
card.style.opacity = 1;
card.style.transform = 'translateY(0)';
}
} else { } else {
card.style.display = 'none'; card.style.display = 'none';
} }
}); });
} }
// Update the menu visibility JavaScript
document.addEventListener('DOMContentLoaded', () => {
const menuToggle = document.querySelector('.menu-toggle');
const sidebar = document.querySelector('.sidebar');
// Force correct initial state based on viewport
function updateMenuState() {
if (window.innerWidth <= 1024) {
menuToggle.style.display = 'flex';
sidebar.style.transform = 'translateX(-100%)';
} else {
menuToggle.style.display = 'none';
sidebar.style.transform = 'translateX(0)';
}
}
// Initial state
updateMenuState();
// Update on resize with debounce
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(updateMenuState, 250);
});
// Toggle sidebar
menuToggle.addEventListener('click', () => {
const isHidden = sidebar.style.transform === 'translateX(-100%)';
sidebar.style.transform = isHidden ? 'translateX(0)' : 'translateX(-100%)';
if (isHidden) {
addOverlay();
} else {
removeOverlay();
}
});
});
</script> </script>
<!-- Code injected by live-server --> <!-- Code injected by live-server -->