first commit
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
/* Custom styles for Finger Web Flask App */
|
||||
|
||||
/* Global styles */
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Navigation enhancements */
|
||||
.navbar-brand {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.navbar-nav .nav-link {
|
||||
font-weight: 500;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.navbar-nav .nav-link:hover {
|
||||
color: #fff !important;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Card enhancements */
|
||||
.card {
|
||||
border: none;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* Jumbotron styling */
|
||||
.jumbotron {
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
/* Button enhancements */
|
||||
.btn {
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(45deg, #007bff, #0056b3);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: linear-gradient(45deg, #0056b3, #004085);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-outline-primary:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* Form enhancements */
|
||||
.form-control {
|
||||
border-radius: 6px;
|
||||
border: 2px solid #e9ecef;
|
||||
transition: border-color 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
|
||||
/* Alert enhancements */
|
||||
.alert {
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background: linear-gradient(45deg, #d4edda, #c3e6cb);
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
background: linear-gradient(45deg, #f8d7da, #f5c6cb);
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
/* Footer styling */
|
||||
footer {
|
||||
margin-top: auto;
|
||||
border-top: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
/* Code block styling */
|
||||
pre {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
pre code {
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.jumbotron {
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
.display-4 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Animation for page load */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
animation: fadeIn 0.6s ease-out;
|
||||
}
|
||||
|
||||
/* Custom utility classes */
|
||||
.text-gradient {
|
||||
background: linear-gradient(45deg, #007bff, #6610f2);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.shadow-custom {
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Loading animation for forms */
|
||||
.btn.loading {
|
||||
position: relative;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.btn.loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: -8px;
|
||||
margin-top: -8px;
|
||||
border: 2px solid #ffffff;
|
||||
border-radius: 50%;
|
||||
border-top-color: transparent;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Finger command specific styles */
|
||||
.finger-output pre {
|
||||
font-family: 'Courier New', Consolas, monospace;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.4;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.finger-output pre::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.finger-output pre::-webkit-scrollbar-track {
|
||||
background: #2d3748;
|
||||
}
|
||||
|
||||
.finger-output pre::-webkit-scrollbar-thumb {
|
||||
background: #4a5568;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.finger-output pre::-webkit-scrollbar-thumb:hover {
|
||||
background: #718096;
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
// Main JavaScript file for Finger Web Flask App
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize all functionality when DOM is loaded
|
||||
initializeFormValidation();
|
||||
initializeNavigation();
|
||||
initializeAnimations();
|
||||
initializeTooltips();
|
||||
});
|
||||
|
||||
// Form validation and enhancement
|
||||
function initializeFormValidation() {
|
||||
const forms = document.querySelectorAll('form');
|
||||
|
||||
forms.forEach(form => {
|
||||
form.addEventListener('submit', function(event) {
|
||||
if (!form.checkValidity()) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
} else {
|
||||
// Add loading state to submit button
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
if (submitBtn) {
|
||||
submitBtn.classList.add('loading');
|
||||
submitBtn.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
form.classList.add('was-validated');
|
||||
});
|
||||
|
||||
// Real-time validation feedback
|
||||
const inputs = form.querySelectorAll('input, textarea');
|
||||
inputs.forEach(input => {
|
||||
input.addEventListener('blur', function() {
|
||||
if (this.checkValidity()) {
|
||||
this.classList.remove('is-invalid');
|
||||
this.classList.add('is-valid');
|
||||
} else {
|
||||
this.classList.remove('is-valid');
|
||||
this.classList.add('is-invalid');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Navigation enhancements
|
||||
function initializeNavigation() {
|
||||
// Highlight current page in navigation
|
||||
const currentPath = window.location.pathname;
|
||||
const navLinks = document.querySelectorAll('.navbar-nav .nav-link');
|
||||
|
||||
navLinks.forEach(link => {
|
||||
if (link.getAttribute('href') === currentPath) {
|
||||
link.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
// Smooth scrolling for anchor links
|
||||
const anchorLinks = document.querySelectorAll('a[href^="#"]');
|
||||
anchorLinks.forEach(link => {
|
||||
link.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
target.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Animation and visual enhancements
|
||||
function initializeAnimations() {
|
||||
// Fade in cards on scroll
|
||||
const observerOptions = {
|
||||
threshold: 0.1,
|
||||
rootMargin: '0px 0px -50px 0px'
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver(function(entries) {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = '1';
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}
|
||||
});
|
||||
}, observerOptions);
|
||||
|
||||
// Observe all cards
|
||||
const cards = document.querySelectorAll('.card');
|
||||
cards.forEach(card => {
|
||||
card.style.opacity = '0';
|
||||
card.style.transform = 'translateY(20px)';
|
||||
card.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
|
||||
observer.observe(card);
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize Bootstrap tooltips
|
||||
function initializeTooltips() {
|
||||
// Enable tooltips if Bootstrap is available
|
||||
if (typeof bootstrap !== 'undefined') {
|
||||
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||
tooltipTriggerList.map(function(tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
function showNotification(message, type = 'info') {
|
||||
// Create a notification element
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
|
||||
notification.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;';
|
||||
notification.innerHTML = `
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Auto-remove after 5 seconds
|
||||
setTimeout(() => {
|
||||
if (notification.parentNode) {
|
||||
notification.remove();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// API interaction helpers
|
||||
async function fetchAPI(endpoint) {
|
||||
try {
|
||||
const response = await fetch(endpoint);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('API fetch error:', error);
|
||||
showNotification('Failed to fetch data from API', 'danger');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Form submission with AJAX (optional enhancement)
|
||||
function submitFormAjax(form, successCallback) {
|
||||
const formData = new FormData(form);
|
||||
|
||||
fetch(form.action, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showNotification(data.message, 'success');
|
||||
if (successCallback) successCallback(data);
|
||||
} else {
|
||||
showNotification(data.message || 'An error occurred', 'danger');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Form submission error:', error);
|
||||
showNotification('Failed to submit form', 'danger');
|
||||
})
|
||||
.finally(() => {
|
||||
// Remove loading state
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
if (submitBtn) {
|
||||
submitBtn.classList.remove('loading');
|
||||
submitBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Keyboard shortcuts
|
||||
document.addEventListener('keydown', function(e) {
|
||||
// Alt + H for Home
|
||||
if (e.altKey && e.key === 'h') {
|
||||
e.preventDefault();
|
||||
window.location.href = '/';
|
||||
}
|
||||
|
||||
// Alt + A for About
|
||||
if (e.altKey && e.key === 'a') {
|
||||
e.preventDefault();
|
||||
window.location.href = '/about';
|
||||
}
|
||||
|
||||
// Alt + F for Finger
|
||||
if (e.altKey && e.key === 'f') {
|
||||
e.preventDefault();
|
||||
window.location.href = '/finger';
|
||||
}
|
||||
});
|
||||
|
||||
// Console welcome message
|
||||
console.log('%c🚀 Finger Web Flask App', 'color: #007bff; font-size: 16px; font-weight: bold;');
|
||||
console.log('%cWelcome to the developer console!', 'color: #6c757d;');
|
||||
console.log('%cKeyboard shortcuts: Alt+H (Home), Alt+A (About), Alt+F (Finger)', 'color: #6c757d;');
|
||||
Reference in New Issue
Block a user