/", # Precomputed route
+ "shortest_route": "/api/routes/shortest/", # Shortest route
+ },
}
- return render(request, 'home.html', context)
+
+ # Check if browser or API client
+ if "text/html" in request.META.get("HTTP_ACCEPT", ""):
+ html_content = f"""
+
+
+ Egypt Metro API
+
+
+
+ Welcome to Egypt Metro Backend
+ Version: {data['version']}
+ Date & Time: {data['current_date_time']}
+ Environment: {data['environment']}
+ Quick Links
+
+ API Routes
+
+ """
+ for name, path in data["api_routes"].items():
+ html_content += f"- {name}
"
+ html_content += "
"
+
+ return HttpResponse(html_content)
+
+ # Return the JSON response with status code 200
+ return JsonResponse(data, status=200)
def health_check(request):
diff --git a/static/css/admin.css b/static/css/admin.css
new file mode 100644
index 0000000..619f073
--- /dev/null
+++ b/static/css/admin.css
@@ -0,0 +1,21 @@
+/* Admin Customization */
+#header {
+ background: var(--primary);
+ color: white;
+}
+
+.module h2, .module caption {
+ background: var(--primary-dark);
+}
+
+div.breadcrumbs {
+ background: var(--primary);
+}
+
+.submit-row input {
+ background: var(--primary);
+}
+
+.submit-row input:hover {
+ background: var(--primary-dark);
+}
\ No newline at end of file
diff --git a/static/css/animations.css b/static/css/animations.css
index 3ffaaa7..b73a3d4 100644
--- a/static/css/animations.css
+++ b/static/css/animations.css
@@ -1,47 +1,124 @@
-/* static/css/animations.css */
-@keyframes fadeInUp {
- from {
- opacity: 0;
- transform: translateY(20px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-@keyframes pulseHighlight {
+/* Add to your existing animation.css */
+
+/* Line Cards Animation */
+.line-card {
+ animation: fadeIn 0.5s ease-out;
+ transition: transform 0.3s ease;
+}
+
+.line-card:hover {
+ transform: translateY(-5px);
+}
+
+/* Developer Card Animation */
+.developer-card {
+ animation: scaleIn 0.8s ease-out;
+}
+
+.developer-avatar {
+ animation: float 3s ease-in-out infinite;
+}
+
+/* API Links Animation */
+.api-link {
+ animation: fadeIn 0.5s ease-out;
+}
+
+.api-link:nth-child(1) { animation-delay: 0.1s; }
+.api-link:nth-child(2) { animation-delay: 0.2s; }
+.api-link:nth-child(3) { animation-delay: 0.3s; }
+
+/* Particle Interaction Animations */
+@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
-.metro-map {
- transition: transform 0.3s ease;
+.interactive-element {
+ animation: pulse 2s infinite;
}
-.metro-map:hover {
- transform: scale(1.02);
+/* Hover Effects */
+.glass-card:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
+ background: rgba(255, 255, 255, 0.15);
}
-.line-path {
- animation: fadeInUp 0.5s ease-out forwards;
+/* Content Fade In */
+.fade-in {
opacity: 0;
+ transform: translateY(20px);
+ animation: fadeIn 0.8s ease forwards;
}
-.line-path:nth-child(1) { animation-delay: 0.2s; }
-.line-path:nth-child(2) { animation-delay: 0.4s; }
-.line-path:nth-child(3) { animation-delay: 0.6s; }
+.delay-1 { animation-delay: 0.2s; }
+.delay-2 { animation-delay: 0.4s; }
+.delay-3 { animation-delay: 0.6s; }
-.api-category {
- animation: fadeInUp 0.5s ease-out forwards;
+/* Image Animations and Effects */
+.responsive-img {
opacity: 0;
+ transition: opacity 0.3s ease, transform 0.3s ease;
}
-.api-category:nth-child(1) { animation-delay: 0.3s; }
-.api-category:nth-child(2) { animation-delay: 0.5s; }
-.api-category:nth-child(3) { animation-delay: 0.7s; }
+.responsive-img.loaded {
+ opacity: 1;
+}
+
+.image-hover {
+ overflow: hidden;
+ position: relative;
+}
+
+.image-hover img {
+ transition: transform 0.3s ease;
+}
+
+.image-hover:hover img {
+ transform: scale(1.05);
+}
+
+/* Skeleton Loading */
+.skeleton {
+ background: linear-gradient(
+ 90deg,
+ rgba(255, 255, 255, 0.1),
+ rgba(255, 255, 255, 0.2),
+ rgba(255, 255, 255, 0.1)
+ );
+ background-size: 200% 100%;
+ animation: loading 1.5s infinite;
+}
+
+@keyframes loading {
+ 0% { background-position: 200% 0; }
+ 100% { background-position: -200% 0; }
+}
+
+@keyframes fadeIn {
+ from { opacity: 0; transform: translateY(20px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+@keyframes scaleIn {
+ from { transform: scale(0.95); opacity: 0; }
+ to { transform: scale(1); opacity: 1; }
+}
+
+.fade-in {
+ animation: fadeIn 0.8s ease forwards;
+}
+
+.scale-in {
+ animation: scaleIn 0.5s ease forwards;
+}
+
+.hover-lift {
+ transition: transform 0.3s ease;
+}
-.interchange-station {
- animation: pulseHighlight 2s infinite;
+.hover-lift:hover {
+ transform: translateY(-5px);
}
\ No newline at end of file
diff --git a/static/css/style.css b/static/css/style.css
index 275dd52..d95ede1 100644
--- a/static/css/style.css
+++ b/static/css/style.css
@@ -1,309 +1,130 @@
-/* Base Styles and Variables */
:root {
- --primary-color: #2563eb;
- --secondary-color: #1e40af;
- --background-color: #ffffff;
- --text-color: #1f2937;
- --hover-color: #3b82f6;
- --shadow-color: rgba(0, 0, 0, 0.1);
- --gray-100: #f3f4f6;
- --gray-200: #e5e7eb;
- --gray-300: #d1d5db;
- --gray-400: #9ca3af;
- --gray-500: #6b7280;
- --success-color: #10b981;
- --error-color: #ef4444;
- --warning-color: #f59e0b;
+ /* Colors */
+ --primary: #2196F3;
+ --secondary: #1976D2;
+ --text: #FFFFFF;
+ --glass-bg: rgba(255, 255, 255, 0.1);
+ --glass-border: rgba(255, 255, 255, 0.2);
+
+ /* Typography */
+ --h1-size: clamp(2.5rem, 5vw, 4rem);
+ --h2-size: clamp(2rem, 4vw, 3rem);
+ --h3-size: clamp(1.5rem, 3vw, 2rem);
+ --body-size: clamp(1rem, 2vw, 1.125rem);
+
+ /* Spacing */
+ --spacing-xs: 0.5rem;
+ --spacing-sm: 1rem;
+ --spacing-md: 2rem;
+ --spacing-lg: 4rem;
+
+ /* Effects */
+ --shadow-md: 0 4px 8px rgba(0,0,0,0.12);
+ --transition: all 0.3s ease;
}
-/* Reset and Base Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
- font-family: 'Poppins', sans-serif;
}
body {
- background-color: var(--gray-100);
+ font-family: 'Inter', sans-serif;
+ font-size: var(--body-size);
line-height: 1.6;
- color: var(--text-color);
- padding-top: 70px; /* Account for fixed header */
+ color: var(--text);
+ background: linear-gradient(135deg, #1a237e, #0d47a1);
+ overflow-x: hidden;
}
-/* Container */
-.container {
- max-width: 1200px;
- margin: 0 auto;
- padding: 0 2rem;
-}
-
-/* Main Content Area */
-.main-content {
- min-height: calc(100vh - 70px - 200px); /* Account for header and footer */
- padding: 2rem 0;
-}
-
-/* Card Styles */
-.card {
- background: var(--background-color);
- border-radius: 12px;
- box-shadow: 0 2px 10px var(--shadow-color);
- padding: 1.5rem;
- margin-bottom: 1.5rem;
- transition: transform 0.3s ease;
-}
-
-.card:hover {
- transform: translateY(-5px);
-}
-
-/* Grid System */
-.grid {
- display: grid;
- gap: 1.5rem;
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
-}
-
-/* Station List */
-.station-list {
- display: grid;
- gap: 1rem;
-}
-
-.station-item {
- background: var(--background-color);
- padding: 1rem;
- border-radius: 8px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- box-shadow: 0 2px 4px var(--shadow-color);
-}
-
-/* Route Map */
-.route-map {
- background: var(--background-color);
- padding: 2rem;
- border-radius: 12px;
- box-shadow: 0 4px 6px var(--shadow-color);
- margin: 2rem 0;
-}
-
-.route-map img {
+#particles-js {
+ position: fixed;
width: 100%;
- height: auto;
- border-radius: 8px;
-}
-
-/* Buttons */
-.btn {
- padding: 0.75rem 1.5rem;
- border-radius: 8px;
- border: none;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.3s ease;
- display: inline-flex;
- align-items: center;
- gap: 0.5rem;
-}
-
-.btn-primary {
- background-color: var(--primary-color);
- color: white;
+ height: 100%;
+ top: 0;
+ left: 0;
+ z-index: 1;
}
-.btn-primary:hover {
- background-color: var(--secondary-color);
- transform: translateY(-2px);
-}
-
-.btn-secondary {
- background-color: var(--gray-200);
- color: var(--text-color);
-}
-
-.btn-secondary:hover {
- background-color: var(--gray-300);
-}
-
-/* Forms */
-.form-group {
- margin-bottom: 1.5rem;
+.site-wrapper {
+ position: relative;
+ z-index: 2;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
}
-.form-label {
- display: block;
- margin-bottom: 0.5rem;
- font-weight: 500;
+.glass {
+ background: var(--glass-bg);
+ -webkit-backdrop-filter: blur(10px);
+ backdrop-filter: blur(10px);
+ border: 1px solid var(--glass-border);
+ box-shadow: var(--shadow-md);
}
-.form-input {
+.main-header {
+ background: var(--glass-bg);
+ -webkit-backdrop-filter: blur(10px);
+ backdrop-filter: blur(10px);
+ border-bottom: 1px solid var(--glass-border);
+ position: fixed;
width: 100%;
- padding: 0.75rem;
- border: 1px solid var(--gray-300);
- border-radius: 8px;
- transition: border-color 0.3s ease;
-}
-
-.form-input:focus {
- outline: none;
- border-color: var(--primary-color);
- box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.1);
+ top: 0;
+ z-index: 1000;
}
-/* Footer */
-.footer {
- background-color: var(--background-color);
- padding: 3rem 0;
- box-shadow: 0 -2px 10px var(--shadow-color);
-}
-
-.footer-content {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
- gap: 2rem;
+.nav-container {
max-width: 1200px;
margin: 0 auto;
- padding: 0 2rem;
-}
-
-.footer-section {
+ padding: var(--spacing-sm) var(--spacing-md);
display: flex;
- flex-direction: column;
- gap: 1rem;
-}
-
-.footer-section h3 {
- color: var(--text-color);
- font-size: 1.2rem;
- margin-bottom: 1rem;
+ justify-content: space-between;
+ align-items: center;
}
-.footer-links {
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
+.main-content {
+ flex: 1;
+ padding: var(--spacing-lg) var(--spacing-md);
+ margin-top: 70px;
}
-.footer-links a {
- color: var(--gray-500);
- text-decoration: none;
- transition: color 0.3s ease;
+.container {
+ max-width: 1200px;
+ margin: 0 auto;
}
-.footer-links a:hover {
- color: var(--primary-color);
+.section {
+ margin: var(--spacing-lg) 0;
}
-.footer-bottom {
- margin-top: 2rem;
- padding-top: 2rem;
- border-top: 1px solid var(--gray-200);
+.section-title {
+ font-size: var(--h2-size);
+ margin-bottom: var(--spacing-md);
text-align: center;
- color: var(--gray-500);
}
-/* Alerts and Messages */
-.alert {
- padding: 1rem;
- border-radius: 8px;
- margin-bottom: 1rem;
-}
-
-.alert-success {
- background-color: #d1fae5;
- color: #065f46;
- border: 1px solid #34d399;
-}
-
-.alert-error {
- background-color: #fee2e2;
- color: #991b1b;
- border: 1px solid #f87171;
+.footer {
+ background: var(--glass-bg);
+ -webkit-backdrop-filter: blur(10px);
+ backdrop-filter: blur(10px);
+ padding: var(--spacing-md) 0;
+ margin-top: auto;
}
-/* Responsive Design */
@media (max-width: 768px) {
- .container {
- padding: 0 1rem;
+ .nav-container {
+ padding: var(--spacing-sm);
}
- .grid {
- grid-template-columns: 1fr;
+ .section-title {
+ font-size: var(--h3-size);
}
-
- .footer-content {
- grid-template-columns: 1fr;
- text-align: center;
- }
-
- .footer-links {
- align-items: center;
- }
-}
-
-/* Admin Customization */
-.admin-container {
- padding: 2rem;
- background: var(--background-color);
- border-radius: 12px;
- box-shadow: 0 2px 10px var(--shadow-color);
-}
-
-.admin-header {
- margin-bottom: 2rem;
- padding-bottom: 1rem;
- border-bottom: 2px solid var(--gray-200);
}
-/* Tables */
-.table-container {
- overflow-x: auto;
-}
-
-.table {
- width: 100%;
- border-collapse: collapse;
- margin: 1rem 0;
-}
-
-.table th,
-.table td {
- padding: 1rem;
- text-align: left;
- border-bottom: 1px solid var(--gray-200);
-}
-
-.table th {
- background-color: var(--gray-100);
- font-weight: 600;
-}
-
-.table tr:hover {
- background-color: var(--gray-50);
-}
-
-/* Badges */
-.badge {
- padding: 0.25rem 0.75rem;
- border-radius: 9999px;
- font-size: 0.875rem;
- font-weight: 500;
-}
-
-.badge-success {
- background-color: #d1fae5;
- color: #065f46;
-}
-
-.badge-warning {
- background-color: #fef3c7;
- color: #92400e;
-}
-
-.badge-error {
- background-color: #fee2e2;
- color: #991b1b;
+@media (prefers-reduced-motion: reduce) {
+ * {
+ animation: none !important;
+ transition: none !important;
+ }
}
\ No newline at end of file
diff --git a/static/images/metro-logo.png b/static/images/metro-map.png
similarity index 100%
rename from static/images/metro-logo.png
rename to static/images/metro-map.png
diff --git a/static/js/animations.js b/static/js/animations.js
new file mode 100644
index 0000000..6932626
--- /dev/null
+++ b/static/js/animations.js
@@ -0,0 +1,36 @@
+// Scroll Animations
+document.addEventListener("DOMContentLoaded", function () {
+ const elements = document.querySelectorAll(".scroll-reveal");
+ const observer = new IntersectionObserver((entries) => {
+ entries.forEach((entry) => {
+ if (entry.isIntersecting) {
+ entry.target.classList.add("visible");
+ }
+ });
+ });
+ elements.forEach((element) => observer.observe(element));
+});
+
+// Particles.js Background
+document.addEventListener("DOMContentLoaded", function () {
+ particlesJS("particles-js", {
+ particles: {
+ number: { value: 80, density: { enable: true, value_area: 800 } },
+ color: { value: "#2196F3" },
+ shape: { type: "circle" },
+ opacity: { value: 0.5, random: true },
+ size: { value: 3, random: true },
+ line_linked: { enable: true, distance: 150, color: "#2196F3", opacity: 0.4, width: 1 },
+ move: { enable: true, speed: 3, direction: "none", random: true, straight: false, out_mode: "out" }
+ },
+ interactivity: {
+ detect_on: "canvas",
+ events: {
+ onhover: { enable: true, mode: "repulse" },
+ onclick: { enable: true, mode: "push" },
+ resize: true
+ }
+ },
+ retina_detect: true
+ });
+});
\ No newline at end of file
diff --git a/static/js/main.js b/static/js/main.js
index 099021c..a08d14a 100644
--- a/static/js/main.js
+++ b/static/js/main.js
@@ -1,36 +1,35 @@
-// static/js/main.js
+// Image loading optimization
document.addEventListener('DOMContentLoaded', function() {
- // Metro map interactivity
- const map = document.querySelector('.metro-map');
- const stations = document.querySelectorAll('.station-point');
+ // Lazy load images
+ const images = document.querySelectorAll('img[data-src]');
- stations.forEach(station => {
- station.addEventListener('click', () => {
- showStationInfo(station.dataset.stationId);
+ const imageObserver = new IntersectionObserver((entries, observer) => {
+ entries.forEach(entry => {
+ if (entry.isIntersecting) {
+ const img = entry.target;
+ img.src = img.dataset.src;
+ img.onload = () => img.classList.add('loaded');
+ observer.unobserve(img);
+ }
});
});
- // API documentation search
- const searchInput = document.querySelector('.api-search');
- const apiEndpoints = document.querySelectorAll('.api-list li');
-
- if (searchInput) {
- searchInput.addEventListener('input', (e) => {
- const searchTerm = e.target.value.toLowerCase();
- apiEndpoints.forEach(endpoint => {
- const text = endpoint.textContent.toLowerCase();
- endpoint.style.display = text.includes(searchTerm) ? 'block' : 'none';
- });
+ images.forEach(img => imageObserver.observe(img));
+
+ // Responsive image sizing
+ function handleResponsiveImages() {
+ const contentWidth = document.querySelector('.container').offsetWidth;
+ const images = document.querySelectorAll('.responsive-img');
+
+ images.forEach(img => {
+ if (contentWidth < 768) {
+ img.style.maxWidth = '100%';
+ } else {
+ img.style.maxWidth = '75%';
+ }
});
}
- // Smooth scrolling for navigation links
- document.querySelectorAll('a[href^="#"]').forEach(anchor => {
- anchor.addEventListener('click', function (e) {
- e.preventDefault();
- document.querySelector(this.getAttribute('href')).scrollIntoView({
- behavior: 'smooth'
- });
- });
- });
+ window.addEventListener('resize', handleResponsiveImages);
+ handleResponsiveImages();
});
\ No newline at end of file
diff --git a/static/js/particles-config.js b/static/js/particles-config.js
new file mode 100644
index 0000000..78bf739
--- /dev/null
+++ b/static/js/particles-config.js
@@ -0,0 +1,94 @@
+// static/js/particles-config.js
+document.addEventListener("DOMContentLoaded", function() {
+ particlesJS("particles-js", {
+ "particles": {
+ "number": {
+ "value": 100,
+ "density": {
+ "enable": true,
+ "value_area": 800
+ }
+ },
+ "color": {
+ "value": "#2196F3"
+ },
+ "shape": {
+ "type": "circle",
+ "stroke": {
+ "width": 0,
+ "color": "#000000"
+ },
+ "polygon": {
+ "nb_sides": 5
+ }
+ },
+ "opacity": {
+ "value": 0.3,
+ "random": true,
+ "anim": {
+ "enable": true,
+ "speed": 1,
+ "opacity_min": 0.1,
+ "sync": false
+ }
+ },
+ "size": {
+ "value": 3,
+ "random": true,
+ "anim": {
+ "enable": true,
+ "speed": 2,
+ "size_min": 0.1,
+ "sync": false
+ }
+ },
+ "line_linked": {
+ "enable": true,
+ "distance": 150,
+ "color": "#2196F3",
+ "opacity": 0.2,
+ "width": 1
+ },
+ "move": {
+ "enable": true,
+ "speed": 2,
+ "direction": "none",
+ "random": true,
+ "straight": false,
+ "out_mode": "out",
+ "bounce": false,
+ "attract": {
+ "enable": true,
+ "rotateX": 600,
+ "rotateY": 1200
+ }
+ }
+ },
+ "interactivity": {
+ "detect_on": "canvas",
+ "events": {
+ "onhover": {
+ "enable": true,
+ "mode": "grab"
+ },
+ "onclick": {
+ "enable": true,
+ "mode": "push"
+ },
+ "resize": true
+ },
+ "modes": {
+ "grab": {
+ "distance": 140,
+ "line_linked": {
+ "opacity": 1
+ }
+ },
+ "push": {
+ "particles_nb": 4
+ }
+ }
+ },
+ "retina_detect": true
+ });
+});
\ No newline at end of file
diff --git a/templates/base.html b/templates/base.html
index e576a5b..97bc75f 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -4,30 +4,36 @@
- {% block title %}Egypt Metro System{% endblock %}
+ {% block title %}Egypt Metro{% endblock %}
-
-
-
-
-
-
+
+
+
-
-
+
+
- {% block extra_css %}{% endblock %}
+
+
+
- {% include 'includes/header.html' %}
+
+
-
- {% block content %}{% endblock %}
-
+
+
+ {% include 'includes/header.html' %}
- {% include 'includes/footer.html' %}
-
-
+
+ {% block content %}{% endblock %}
+
+
+ {% include 'includes/footer.html' %}
+
+
+
+
{% block extra_js %}{% endblock %}