diff --git a/.gitignore b/.gitignore index ea8e73d..749c55f 100644 --- a/.gitignore +++ b/.gitignore @@ -155,6 +155,8 @@ dmypy.json # Cython debug symbols cython_debug/ +/staticfiles/ + # PyCharm # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore diff --git a/admin_panel_config.py b/admin_panel_config.py new file mode 100644 index 0000000..5d22165 --- /dev/null +++ b/admin_panel_config.py @@ -0,0 +1,139 @@ +""" +Configuración del Panel de Administración +Variables de entorno y configuraciones personalizables +""" + +import os +from django.conf import settings + +# Configuración del Panel de Administración +ADMIN_PANEL_CONFIG = { + "enabled": os.getenv("ADMIN_PANEL_ENABLED", "True").lower() == "true", + "refresh_interval": int(os.getenv("ADMIN_REFRESH_INTERVAL", "30000")), + "chart_height": int(os.getenv("ADMIN_CHART_HEIGHT", "300")), + "table_page_size": int(os.getenv("ADMIN_TABLE_PAGE_SIZE", "20")), + "max_recent_items": int(os.getenv("ADMIN_MAX_RECENT_ITEMS", "10")), +} + +# Configuración de Análisis +ANALYTICS_CONFIG = { + "min_data_points": int(os.getenv("ANALYTICS_MIN_DATA_POINTS", "10")), + "confidence_thresholds": { + "high": float(os.getenv("ANALYTICS_CONFIDENCE_HIGH", "0.8")), + "medium": float(os.getenv("ANALYTICS_CONFIDENCE_MEDIUM", "0.6")), + "low": float(os.getenv("ANALYTICS_CONFIDENCE_LOW", "0.4")), + }, +} + +# Configuración de Alertas +ALERT_CONFIG = { + "low_activity_threshold": int(os.getenv("ALERT_LOW_ACTIVITY_THRESHOLD", "5")), + "high_bid_threshold": int(os.getenv("ALERT_HIGH_BID_THRESHOLD", "1000")), + "suspicious_activity_threshold": int( + os.getenv("ALERT_SUSPICIOUS_ACTIVITY_THRESHOLD", "50") + ), +} + +# Configuración de Exportación +EXPORT_CONFIG = { + "max_records": int(os.getenv("EXPORT_MAX_RECORDS", "10000")), + "date_format": os.getenv("EXPORT_DATE_FORMAT", "%Y-%m-%d %H:%M:%S"), + "supported_formats": ["csv", "xlsx", "json"], +} + +# Configuración de Colores +CHART_COLORS = { + "primary": os.getenv("CHART_PRIMARY_COLOR", "#667eea"), + "secondary": os.getenv("CHART_SECONDARY_COLOR", "#764ba2"), + "success": os.getenv("CHART_SUCCESS_COLOR", "#4facfe"), + "info": os.getenv("CHART_INFO_COLOR", "#00f2fe"), + "warning": os.getenv("CHART_WARNING_COLOR", "#f093fb"), + "danger": os.getenv("CHART_DANGER_COLOR", "#f5576c"), + "light": os.getenv("CHART_LIGHT_COLOR", "#a8edea"), + "dark": os.getenv("CHART_DARK_COLOR", "#fed6e3"), +} + +# Configuración de Base de Datos para Análisis +DATABASE_CONFIG = { + "query_timeout": int(os.getenv("DB_QUERY_TIMEOUT", "30")), + "max_connections": int(os.getenv("DB_MAX_CONNECTIONS", "20")), + "enable_query_logging": os.getenv("DB_ENABLE_QUERY_LOGGING", "False").lower() + == "true", +} + +# Configuración de Caché +CACHE_CONFIG = { + "enabled": os.getenv("CACHE_ENABLED", "True").lower() == "true", + "timeout": int(os.getenv("CACHE_TIMEOUT", "300")), # 5 minutos + "key_prefix": os.getenv("CACHE_KEY_PREFIX", "admin_panel"), +} + +# Configuración de Logging +LOGGING_CONFIG = { + "level": os.getenv("LOG_LEVEL", "INFO"), + "file": os.getenv("LOG_FILE", "admin_panel.log"), + "max_size": int(os.getenv("LOG_MAX_SIZE", "10485760")), # 10MB + "backup_count": int(os.getenv("LOG_BACKUP_COUNT", "5")), +} + + +def get_config(section): + """ + Obtener configuración de una sección específica + """ + configs = { + "admin_panel": ADMIN_PANEL_CONFIG, + "analytics": ANALYTICS_CONFIG, + "alerts": ALERT_CONFIG, + "export": EXPORT_CONFIG, + "colors": CHART_COLORS, + "database": DATABASE_CONFIG, + "cache": CACHE_CONFIG, + "logging": LOGGING_CONFIG, + } + + return configs.get(section, {}) + + +def is_feature_enabled(feature): + """ + Verificar si una funcionalidad está habilitada + """ + feature_configs = { + "admin_panel": ADMIN_PANEL_CONFIG["enabled"], + "analytics": True, + "alerts": True, + "export": True, + "caching": CACHE_CONFIG["enabled"], + "query_logging": DATABASE_CONFIG["enable_query_logging"], + } + + return feature_configs.get(feature, False) + + +def get_chart_color(color_name): + """ + Obtener color para gráficos + """ + return CHART_COLORS.get(color_name, "#667eea") + + +def get_alert_threshold(alert_type): + """ + Obtener umbral para alertas + """ + return ALERT_CONFIG.get(alert_type, 0) + + +def get_export_limit(): + """ + Obtener límite de exportación + """ + return EXPORT_CONFIG["max_records"] + + +def get_refresh_interval(): + """ + Obtener intervalo de actualización + """ + return ADMIN_PANEL_CONFIG["refresh_interval"] diff --git a/auctions/admin_config.py b/auctions/admin_config.py new file mode 100644 index 0000000..1447cfe --- /dev/null +++ b/auctions/admin_config.py @@ -0,0 +1,44 @@ +""" +Configuración del panel de administración +Configuraciones específicas para el dashboard de BI +""" + +# Configuración de colores para gráficos +CHART_COLORS = { + "primary": "#667eea", + "secondary": "#764ba2", + "success": "#4facfe", + "info": "#00f2fe", + "warning": "#f093fb", + "danger": "#f5576c", + "light": "#a8edea", + "dark": "#fed6e3", +} + +# Configuración de métricas +METRICS_CONFIG = { + "refresh_interval": 30000, # 30 segundos + "chart_height": 300, + "table_page_size": 20, + "max_recent_items": 10, +} + +# Configuración de exportación +EXPORT_CONFIG = { + "supported_formats": ["csv", "xlsx"], + "max_records_per_export": 10000, + "date_format": "%Y-%m-%d %H:%M:%S", +} + +# Configuración de análisis predictivo +PREDICTION_CONFIG = { + "min_data_points": 10, + "confidence_thresholds": {"high": 0.8, "medium": 0.6, "low": 0.4}, +} + +# Configuración de alertas +ALERT_CONFIG = { + "low_activity_threshold": 5, # días sin actividad + "high_bid_threshold": 1000, # pujas muy altas + "suspicious_activity_threshold": 50, # pujas por usuario por día +} diff --git a/auctions/admin_views.py b/auctions/admin_views.py new file mode 100644 index 0000000..46aaac0 --- /dev/null +++ b/auctions/admin_views.py @@ -0,0 +1,442 @@ +""" +Vistas del panel de administración para superusuarios +Dashboard de Business Intelligence para gestión de subastas +""" + +from django.shortcuts import render, get_object_or_404, redirect +from django.contrib.admin.views.decorators import staff_member_required +from django.contrib.auth.decorators import user_passes_test +from django.http import JsonResponse +from django.utils import timezone +from django.db.models import Q, Count, Sum, Avg +from django.core.paginator import Paginator +from django.contrib import messages +from .models import Listing, Bid, Comment, User, Watchlist +from .analytics import AuctionAnalytics +from .error_views import test_404_view, test_500_view, test_403_view +import json + + +def is_superuser(user): + """Verificar si el usuario es superusuario""" + return user.is_superuser + + +@user_passes_test(is_superuser) +def admin_dashboard(request): + """ + Dashboard principal de administración con métricas y gráficos + """ + try: + analytics = AuctionAnalytics() + dashboard_data = analytics.get_kpi_dashboard_data() + + # Obtener subastas recientes + recent_listings = Listing.objects.select_related("user").order_by("-created")[ + :10 + ] + + # Obtener pujas recientes + recent_bids = Bid.objects.select_related("user", "listing").order_by("-id")[:10] + + # Obtener usuarios más activos + top_users = ( + User.objects.annotate( + total_activity=Count("bids") + Count("listings") + Count("comments") + ) + .filter(total_activity__gt=0) + .order_by("-total_activity")[:5] + ) + + context = { + "dashboard_data": dashboard_data, + "recent_listings": recent_listings, + "recent_bids": recent_bids, + "top_users": top_users, + "page_title": "Dashboard de Administración", + } + + return render(request, "auctions/admin/dashboard.html", context) + except Exception as e: + # En caso de error, mostrar una página simple de administración + context = { + "page_title": "Dashboard de Administración", + "error": str(e), + "dashboard_data": { + "metrics": { + "total_listings": Listing.objects.count(), + "active_listings": Listing.objects.filter(active=True).count(), + "total_users": User.objects.count(), + "total_bids": Bid.objects.count(), + } + }, + "recent_listings": [], + "recent_bids": [], + "top_users": [], + } + return render(request, "auctions/admin/dashboard.html", context) + + +@user_passes_test(is_superuser) +def admin_analytics(request): + """ + Página de análisis avanzados y reportes + """ + analytics = AuctionAnalytics() + + # Análisis por categorías + category_analysis = analytics.get_category_analysis() + + # Análisis de comportamiento de usuarios + user_behavior = analytics.get_user_behavior_analysis() + + # Análisis de pujas + bid_analysis = analytics.get_bid_analysis() + + # Tendencias del mercado + market_trends = analytics.get_market_trends() + + context = { + "category_analysis": category_analysis, + "user_behavior": user_behavior, + "bid_analysis": bid_analysis, + "market_trends": market_trends, + "page_title": "Análisis Avanzados", + } + + return render(request, "auctions/admin/analytics.html", context) + + +@user_passes_test(is_superuser) +def admin_listings(request): + """ + Gestión de subastas con filtros y búsqueda + """ + listings = Listing.objects.select_related("user").prefetch_related("bids") + + # Filtros + search_query = request.GET.get("search", "") + category_filter = request.GET.get("category", "") + status_filter = request.GET.get("status", "") + sort_by = request.GET.get("sort", "-created") + + if search_query: + listings = listings.filter( + Q(title__icontains=search_query) + | Q(description__icontains=search_query) + | Q(user__username__icontains=search_query) + ) + + if category_filter: + listings = listings.filter(category=category_filter) + + if status_filter == "active": + listings = listings.filter(active=True) + elif status_filter == "inactive": + listings = listings.filter(active=False) + elif status_filter == "with_bids": + listings = listings.filter(bids__isnull=False).distinct() + + listings = listings.order_by(sort_by) + + # Paginación + paginator = Paginator(listings, 20) + page_number = request.GET.get("page") + page_obj = paginator.get_page(page_number) + + # Obtener categorías únicas para el filtro + categories = ( + Listing.objects.values_list("category", flat=True) + .distinct() + .exclude(category="") + ) + + context = { + "page_obj": page_obj, + "search_query": search_query, + "category_filter": category_filter, + "status_filter": status_filter, + "sort_by": sort_by, + "categories": categories, + "page_title": "Gestión de Subastas", + } + + return render(request, "auctions/admin/listings.html", context) + + +@user_passes_test(is_superuser) +def admin_users(request): + """ + Gestión de usuarios con estadísticas + """ + users = User.objects.annotate( + listings_count=Count("listings"), + bids_count=Count("bids"), + comments_count=Count("comments"), + watchlist_count=Count("watchlist", filter=Q(watchlist__active=True)), + ).order_by("-date_joined") + + # Filtros + search_query = request.GET.get("search", "") + sort_by = request.GET.get("sort", "-date_joined") + + if search_query: + users = users.filter( + Q(username__icontains=search_query) + | Q(email__icontains=search_query) + | Q(first_name__icontains=search_query) + | Q(last_name__icontains=search_query) + ) + + users = users.order_by(sort_by) + + # Paginación + paginator = Paginator(users, 20) + page_number = request.GET.get("page") + page_obj = paginator.get_page(page_number) + + context = { + "page_obj": page_obj, + "search_query": search_query, + "sort_by": sort_by, + "page_title": "Gestión de Usuarios", + } + + return render(request, "auctions/admin/users.html", context) + + +@user_passes_test(is_superuser) +def admin_listing_detail(request, listing_id): + """ + Detalle de una subasta con análisis predictivo + """ + listing = get_object_or_404(Listing, id=listing_id) + analytics = AuctionAnalytics() + + # Obtener pujas de la subasta + bids = listing.bids.select_related("user").order_by("-amount") + + # Obtener comentarios + comments = listing.comments.select_related("user").order_by("-created") + + # Análisis predictivo + prediction = analytics.predict_auction_success(listing_id) + + # Estadísticas de la subasta + listing_stats = { + "total_bids": bids.count(), + "unique_bidders": bids.values("user").distinct().count(), + "price_increase": 0, + "days_active": (timezone.now() - listing.created).days, + } + + if listing.current_bid and listing.starting_bid: + listing_stats["price_increase"] = ( + (listing.current_bid - listing.starting_bid) / listing.starting_bid * 100 + ) + + context = { + "listing": listing, + "bids": bids, + "comments": comments, + "prediction": prediction, + "listing_stats": listing_stats, + "page_title": f"Detalle: {listing.title}", + } + + return render(request, "auctions/admin/listing_detail.html", context) + + +@user_passes_test(is_superuser) +def admin_reports(request): + """ + Generación de reportes y exportación de datos + """ + analytics = AuctionAnalytics() + + # Métricas básicas + basic_metrics = analytics.get_basic_metrics() + + # Análisis temporal + time_analysis = analytics.get_time_series_data(90) # Últimos 90 días + + # Análisis de categorías + category_analysis = analytics.get_category_analysis() + + # Análisis de usuarios + user_analysis = analytics.get_user_behavior_analysis() + + context = { + "basic_metrics": basic_metrics, + "time_analysis": time_analysis, + "category_analysis": category_analysis, + "user_analysis": user_analysis, + "page_title": "Reportes y Exportación", + } + + return render(request, "auctions/admin/reports.html", context) + + +@user_passes_test(is_superuser) +def admin_api_metrics(request): + """ + API endpoint para métricas en tiempo real (AJAX) + """ + analytics = AuctionAnalytics() + metrics = analytics.get_basic_metrics() + + return JsonResponse(metrics) + + +@user_passes_test(is_superuser) +def admin_api_charts(request): + """ + API endpoint para datos de gráficos (AJAX) + """ + analytics = AuctionAnalytics() + chart_type = request.GET.get("type", "trends") + + if chart_type == "trends": + data = analytics.get_time_series_data(30) + elif chart_type == "categories": + data = analytics.get_category_analysis() + elif chart_type == "bids": + data = analytics.get_bid_analysis() + else: + data = {} + + return JsonResponse(data, safe=False) + + +@user_passes_test(is_superuser) +def admin_toggle_listing_status(request, listing_id): + """ + Activar/desactivar una subasta + """ + if request.method == "POST": + listing = get_object_or_404(Listing, id=listing_id) + listing.active = not listing.active + listing.save() + + status = "activada" if listing.active else "desactivada" + messages.success(request, f"Subasta {status} exitosamente.") + + return redirect("admin_listing_detail", listing_id=listing_id) + + +@user_passes_test(is_superuser) +def admin_export_data(request): + """ + Exportar datos del sistema + """ + import csv + from django.http import HttpResponse + + export_type = request.GET.get("type", "listings") + + response = HttpResponse(content_type="text/csv") + response["Content-Disposition"] = f'attachment; filename="{export_type}_export.csv"' + + writer = csv.writer(response) + + if export_type == "listings": + writer.writerow( + [ + "ID", + "Título", + "Usuario", + "Precio Inicial", + "Precio Actual", + "Categoría", + "Activa", + "Fecha Creación", + ] + ) + for listing in Listing.objects.select_related("user"): + writer.writerow( + [ + listing.id, + listing.title, + listing.user.username, + listing.starting_bid, + listing.current_bid, + listing.category, + listing.active, + listing.created.strftime("%Y-%m-%d %H:%M:%S"), + ] + ) + + elif export_type == "bids": + writer.writerow(["ID", "Usuario", "Subasta", "Monto", "Fecha"]) + for bid in Bid.objects.select_related("user", "listing"): + writer.writerow( + [ + bid.id, + bid.user.username, + bid.listing.title, + bid.amount, + bid.listing.created.strftime("%Y-%m-%d %H:%M:%S"), + ] + ) + + elif export_type == "users": + writer.writerow( + [ + "ID", + "Usuario", + "Email", + "Nombre", + "Apellido", + "Fecha Registro", + "Es Staff", + "Es Superusuario", + ] + ) + for user in User.objects.all(): + writer.writerow( + [ + user.id, + user.username, + user.email, + user.first_name, + user.last_name, + user.date_joined.strftime("%Y-%m-%d %H:%M:%S"), + user.is_staff, + user.is_superuser, + ] + ) + + return response + + +def catch_all_404_view(request, path): + """ + Vista que captura todas las URLs no encontradas y muestra nuestra página 404 personalizada + """ + from .error_views import custom_404_view + + return custom_404_view(request, None) + + +def test_admin_dashboard(request): + """ + Vista de prueba para el dashboard de administración (sin autenticación) + """ + try: + context = { + "page_title": "Dashboard de Administración - Prueba", + "dashboard_data": { + "metrics": { + "total_listings": Listing.objects.count(), + "active_listings": Listing.objects.filter(active=True).count(), + "total_users": User.objects.count(), + "total_bids": Bid.objects.count(), + } + }, + "recent_listings": [], + "recent_bids": [], + "top_users": [], + } + return render(request, "auctions/admin/dashboard.html", context) + except Exception as e: + return render(request, "auctions/errors/500.html", {"error": str(e)}) diff --git a/auctions/analytics.py b/auctions/analytics.py new file mode 100644 index 0000000..efcba02 --- /dev/null +++ b/auctions/analytics.py @@ -0,0 +1,407 @@ +""" +Módulo de análisis de datos para el dashboard de Business Intelligence +Implementa métodos de data science para análisis de subastas +""" + +import pandas as pd +import numpy as np +from datetime import datetime, timedelta +from django.db.models import Count, Sum, Avg, Q, F +from django.utils import timezone +from django.contrib.auth.models import User +from .models import Listing, Bid, Comment, Watchlist +from sklearn.linear_model import LinearRegression +from sklearn.preprocessing import StandardScaler +import plotly.graph_objects as go +import plotly.express as px +from plotly.offline import plot +import json + + +class AuctionAnalytics: + """ + Clase principal para análisis de datos de subastas + Implementa métodos de data science para insights de negocio + """ + + def __init__(self): + self.timezone = timezone.now() + + def get_basic_metrics(self): + """ + Métricas básicas del sistema de subastas + """ + total_listings = Listing.objects.count() + active_listings = Listing.objects.filter(active=True).count() + total_users = User.objects.count() + total_bids = Bid.objects.count() + total_comments = Comment.objects.count() + total_watchlist_items = Watchlist.objects.filter(active=True).count() + + # Calcular valor total de subastas + total_auction_value = ( + Listing.objects.aggregate(total_value=Sum("current_bid"))["total_value"] + or 0 + ) + + # Calcular valor promedio de subastas + avg_auction_value = ( + Listing.objects.filter(current_bid__isnull=False).aggregate( + avg_value=Avg("current_bid") + )["avg_value"] + or 0 + ) + + return { + "total_listings": total_listings, + "active_listings": active_listings, + "total_users": total_users, + "total_bids": total_bids, + "total_comments": total_comments, + "total_watchlist_items": total_watchlist_items, + "total_auction_value": float(total_auction_value), + "avg_auction_value": float(avg_auction_value), + "conversion_rate": ( + (total_bids / total_listings * 100) if total_listings > 0 else 0 + ), + } + + def get_time_series_data(self, days=30): + """ + Datos de series temporales para análisis de tendencias + """ + end_date = self.timezone + start_date = end_date - timedelta(days=days) + + # Listings por día + listings_by_day = ( + Listing.objects.filter(created__gte=start_date) + .extra(select={"day": "date(created)"}) + .values("day") + .annotate(count=Count("id")) + .order_by("day") + ) + + # Bids por día + bids_by_day = ( + Bid.objects.filter(listing__created__gte=start_date) + .extra(select={"day": "date(listing__created)"}) + .values("day") + .annotate(count=Count("id"), total_amount=Sum("amount")) + .order_by("day") + ) + + # Usuarios registrados por día + users_by_day = ( + User.objects.filter(date_joined__gte=start_date) + .extra(select={"day": "date(date_joined)"}) + .values("day") + .annotate(count=Count("id")) + .order_by("day") + ) + + return { + "listings": list(listings_by_day), + "bids": list(bids_by_day), + "users": list(users_by_day), + } + + def get_category_analysis(self): + """ + Análisis por categorías de subastas + """ + category_data = ( + Listing.objects.values("category") + .annotate( + count=Count("id"), + avg_starting_bid=Avg("starting_bid"), + avg_current_bid=Avg("current_bid"), + total_bids=Count("bids"), + avg_bids_per_listing=Count("bids") / Count("id"), + ) + .order_by("-count") + ) + + return list(category_data) + + def get_user_behavior_analysis(self): + """ + Análisis del comportamiento de usuarios + """ + # Top usuarios por actividad + top_bidders = User.objects.annotate( + bid_count=Count("bids"), + total_bid_amount=Sum("bids__amount"), + listings_created=Count("listings"), + comments_made=Count("comments"), + ).order_by("-bid_count")[:10] + + # Análisis de engagement + user_engagement = ( + User.objects.annotate( + total_activity=Count("bids") + Count("listings") + Count("comments"), + watchlist_items=Count("watchlist", filter=Q(watchlist__active=True)), + ) + .filter(total_activity__gt=0) + .order_by("-total_activity") + ) + + return { + "top_bidders": list(top_bidders.values()), + "user_engagement": list(user_engagement.values()), + } + + def get_bid_analysis(self): + """ + Análisis detallado de pujas + """ + # Distribución de pujas por rango de valores + bid_ranges = [ + (0, 50, "0-50"), + (50, 100, "50-100"), + (100, 500, "100-500"), + (500, 1000, "500-1000"), + (1000, float("inf"), "1000+"), + ] + + bid_distribution = [] + for min_val, max_val, label in bid_ranges: + if max_val == float("inf"): + count = Bid.objects.filter(amount__gte=min_val).count() + else: + count = Bid.objects.filter( + amount__gte=min_val, amount__lt=max_val + ).count() + bid_distribution.append({"range": label, "count": count}) + + # Análisis de competencia por listing + listing_competition = ( + Listing.objects.annotate( + bid_count=Count("bids"), + bid_increase=( + (F("current_bid") - F("starting_bid")) / F("starting_bid") * 100 + ), + ) + .filter(bid_count__gt=0) + .order_by("-bid_count") + ) + + return { + "bid_distribution": bid_distribution, + "listing_competition": list(listing_competition.values()), + } + + def predict_auction_success(self, listing_id): + """ + Predicción de éxito de una subasta usando machine learning + """ + try: + # Obtener datos históricos para entrenar el modelo + historical_data = ( + Listing.objects.filter(current_bid__isnull=False) + .annotate( + bid_count=Count("bids"), + days_active=(self.timezone - F("created")).days, + price_increase=( + (F("current_bid") - F("starting_bid")) / F("starting_bid") * 100 + ), + ) + .values("starting_bid", "bid_count", "days_active", "price_increase") + ) + + if len(historical_data) < 10: + return {"error": "Datos insuficientes para predicción"} + + # Preparar datos para el modelo + df = pd.DataFrame(historical_data) + X = df[["starting_bid", "bid_count", "days_active"]] + y = df["price_increase"] + + # Entrenar modelo + scaler = StandardScaler() + X_scaled = scaler.fit_transform(X) + + model = LinearRegression() + model.fit(X_scaled, y) + + # Obtener datos de la subasta actual + listing = Listing.objects.get(id=listing_id) + current_bid_count = listing.bids.count() + days_active = (self.timezone - listing.created).days + + # Hacer predicción + prediction_data = np.array( + [[float(listing.starting_bid), current_bid_count, days_active]] + ) + prediction_scaled = scaler.transform(prediction_data) + predicted_increase = model.predict(prediction_scaled)[0] + + return { + "predicted_price_increase": float(predicted_increase), + "confidence": "medium", # Simplificado para este ejemplo + "recommendations": self._get_recommendations( + predicted_increase, current_bid_count + ), + } + + except Exception as e: + return {"error": f"Error en predicción: {str(e)}"} + + def _get_recommendations(self, price_increase, bid_count): + """ + Generar recomendaciones basadas en el análisis + """ + recommendations = [] + + if price_increase < 10: + recommendations.append("Considera ajustar el precio inicial") + elif price_increase > 100: + recommendations.append( + "Excelente rendimiento, considera estrategias similares" + ) + + if bid_count < 3: + recommendations.append( + "Promociona más la subasta para aumentar participación" + ) + elif bid_count > 10: + recommendations.append("Alta competencia, considera extender el tiempo") + + return recommendations + + def get_market_trends(self): + """ + Análisis de tendencias del mercado + """ + # Análisis mensual + monthly_data = ( + Listing.objects.extra(select={"month": 'strftime("%Y-%m", created)'}) + .values("month") + .annotate( + listings_count=Count("id"), + avg_starting_bid=Avg("starting_bid"), + avg_current_bid=Avg("current_bid"), + total_bids=Count("bids"), + ) + .order_by("month") + ) + + # Análisis de estacionalidad + seasonal_data = ( + Listing.objects.extra(select={"month": 'strftime("%m", created)'}) + .values("month") + .annotate(count=Count("id")) + .order_by("month") + ) + + return { + "monthly_trends": list(monthly_data), + "seasonal_patterns": list(seasonal_data), + } + + def generate_plotly_charts(self): + """ + Generar gráficos interactivos con Plotly + """ + charts = {} + + # 1. Gráfico de líneas - Tendencias temporales + time_data = self.get_time_series_data(30) + if time_data["listings"]: + fig_trends = go.Figure() + fig_trends.add_trace( + go.Scatter( + x=[item["day"] for item in time_data["listings"]], + y=[item["count"] for item in time_data["listings"]], + mode="lines+markers", + name="Listings", + line=dict(color="#007bff"), + ) + ) + fig_trends.add_trace( + go.Scatter( + x=[item["day"] for item in time_data["bids"]], + y=[item["count"] for item in time_data["bids"]], + mode="lines+markers", + name="Bids", + line=dict(color="#28a745"), + ) + ) + fig_trends.update_layout( + title="Tendencias de Actividad (30 días)", + xaxis_title="Fecha", + yaxis_title="Cantidad", + template="plotly_white", + ) + charts["trends"] = plot( + fig_trends, output_type="div", include_plotlyjs=False + ) + + # 2. Gráfico de barras - Categorías + category_data = self.get_category_analysis() + if category_data: + fig_categories = px.bar( + x=[item["category"] or "Sin categoría" for item in category_data], + y=[item["count"] for item in category_data], + title="Listings por Categoría", + labels={"x": "Categoría", "y": "Cantidad"}, + ) + charts["categories"] = plot( + fig_categories, output_type="div", include_plotlyjs=False + ) + + # 3. Gráfico de dispersión - Precio vs Pujas + listing_data = ( + Listing.objects.filter(current_bid__isnull=False) + .annotate(bid_count=Count("bids")) + .values("starting_bid", "current_bid", "bid_count") + ) + + if listing_data: + df = pd.DataFrame(list(listing_data)) + fig_scatter = px.scatter( + df, + x="starting_bid", + y="current_bid", + size="bid_count", + title="Precio Inicial vs Precio Actual", + labels={ + "starting_bid": "Precio Inicial", + "current_bid": "Precio Actual", + }, + hover_data=["bid_count"], + ) + charts["price_analysis"] = plot( + fig_scatter, output_type="div", include_plotlyjs=False + ) + + return charts + + def get_kpi_dashboard_data(self): + """ + Datos consolidados para el dashboard principal + """ + metrics = self.get_basic_metrics() + time_data = self.get_time_series_data(7) # Últimos 7 días + category_data = self.get_category_analysis() + + # Calcular métricas de crecimiento + current_week_listings = sum(item["count"] for item in time_data["listings"]) + previous_week = self.timezone - timedelta(days=14) + previous_week_data = Listing.objects.filter( + created__gte=previous_week, created__lt=self.timezone - timedelta(days=7) + ).count() + + growth_rate = ( + ((current_week_listings - previous_week_data) / previous_week_data * 100) + if previous_week_data > 0 + else 0 + ) + + return { + "metrics": metrics, + "growth_rate": growth_rate, + "top_categories": category_data[:5], + "recent_activity": time_data, + "charts": self.generate_plotly_charts(), + } diff --git a/auctions/data_utils.py b/auctions/data_utils.py new file mode 100644 index 0000000..570c97f --- /dev/null +++ b/auctions/data_utils.py @@ -0,0 +1,333 @@ +""" +Utilidades para análisis de datos y Business Intelligence +Funciones auxiliares para el dashboard de administración +""" + +import pandas as pd +import numpy as np +from datetime import datetime, timedelta +from django.utils import timezone +from django.db.models import Q, Count, Sum, Avg, Max, Min +from .models import Listing, Bid, Comment, User, Watchlist + + +class DataProcessor: + """ + Clase para procesamiento y análisis de datos + """ + + @staticmethod + def calculate_growth_rate(current_value, previous_value): + """ + Calcular tasa de crecimiento entre dos valores + """ + if previous_value == 0: + return 0 + return ((current_value - previous_value) / previous_value) * 100 + + @staticmethod + def get_time_periods(days=30): + """ + Obtener períodos de tiempo para análisis + """ + end_date = timezone.now() + start_date = end_date - timedelta(days=days) + + return {"start": start_date, "end": end_date, "days": days} + + @staticmethod + def calculate_engagement_score(user): + """ + Calcular score de engagement de un usuario + """ + listings_count = user.listings.count() + bids_count = user.bids.count() + comments_count = user.comments.count() + watchlist_count = user.watchlist.filter(active=True).count() + + # Peso de cada actividad + weights = {"listings": 3, "bids": 2, "comments": 1, "watchlist": 1} + + score = ( + listings_count * weights["listings"] + + bids_count * weights["bids"] + + comments_count * weights["comments"] + + watchlist_count * weights["watchlist"] + ) + + return score + + @staticmethod + def detect_anomalies(data, threshold=2): + """ + Detectar anomalías en los datos usando desviación estándar + """ + if len(data) < 3: + return [] + + mean = np.mean(data) + std = np.std(data) + + anomalies = [] + for i, value in enumerate(data): + if abs(value - mean) > threshold * std: + anomalies.append( + {"index": i, "value": value, "deviation": abs(value - mean) / std} + ) + + return anomalies + + @staticmethod + def calculate_market_volatility(listings_data): + """ + Calcular volatilidad del mercado basada en precios + """ + if not listings_data: + return 0 + + prices = [ + listing["current_bid"] or listing["starting_bid"] + for listing in listings_data + ] + if len(prices) < 2: + return 0 + + returns = [] + for i in range(1, len(prices)): + if prices[i - 1] != 0: + returns.append((prices[i] - prices[i - 1]) / prices[i - 1]) + + if not returns: + return 0 + + return np.std(returns) * 100 # Volatilidad como porcentaje + + +class ReportGenerator: + """ + Generador de reportes y análisis + """ + + @staticmethod + def generate_user_activity_report(user_id=None, days=30): + """ + Generar reporte de actividad de usuarios + """ + time_period = DataProcessor.get_time_periods(days) + + if user_id: + users = User.objects.filter(id=user_id) + else: + users = User.objects.all() + + report_data = [] + for user in users: + # Actividad en el período + listings = user.listings.filter(created__gte=time_period["start"]) + bids = user.bids.filter(listing__created__gte=time_period["start"]) + comments = user.comments.filter(created__gte=time_period["start"]) + + # Métricas + total_activity = listings.count() + bids.count() + comments.count() + engagement_score = DataProcessor.calculate_engagement_score(user) + + report_data.append( + { + "user_id": user.id, + "username": user.username, + "email": user.email, + "listings_created": listings.count(), + "bids_made": bids.count(), + "comments_made": comments.count(), + "total_activity": total_activity, + "engagement_score": engagement_score, + "last_activity": user.last_login or user.date_joined, + } + ) + + return sorted(report_data, key=lambda x: x["engagement_score"], reverse=True) + + @staticmethod + def generate_market_analysis(days=30): + """ + Generar análisis del mercado + """ + time_period = DataProcessor.get_time_periods(days) + + # Datos de subastas + listings = Listing.objects.filter(created__gte=time_period["start"]) + + # Métricas básicas + total_listings = listings.count() + active_listings = listings.filter(active=True).count() + closed_listings = listings.filter(active=False).count() + + # Análisis de precios + price_data = listings.filter(current_bid__isnull=False).values( + "starting_bid", "current_bid" + ) + if price_data: + avg_starting = sum(item["starting_bid"] for item in price_data) / len( + price_data + ) + avg_current = sum(item["current_bid"] for item in price_data) / len( + price_data + ) + price_increase = ( + ((avg_current - avg_starting) / avg_starting * 100) + if avg_starting > 0 + else 0 + ) + else: + avg_starting = avg_current = price_increase = 0 + + # Análisis de competencia + competition_data = listings.annotate(bid_count=Count("bids")).filter( + bid_count__gt=0 + ) + + avg_competition = competition_data.aggregate(avg=Avg("bid_count"))["avg"] or 0 + + # Análisis de categorías + category_analysis = ( + listings.values("category") + .annotate(count=Count("id"), avg_price=Avg("current_bid")) + .order_by("-count") + ) + + return { + "period": f"{days} días", + "total_listings": total_listings, + "active_listings": active_listings, + "closed_listings": closed_listings, + "avg_starting_price": round(avg_starting, 2), + "avg_current_price": round(avg_current, 2), + "price_increase_percent": round(price_increase, 2), + "avg_competition": round(avg_competition, 2), + "category_breakdown": list(category_analysis), + "market_volatility": DataProcessor.calculate_market_volatility( + list(price_data) + ), + } + + @staticmethod + def generate_performance_metrics(days=30): + """ + Generar métricas de rendimiento + """ + time_period = DataProcessor.get_time_periods(days) + + # Métricas de conversión + total_listings = Listing.objects.filter( + created__gte=time_period["start"] + ).count() + listings_with_bids = ( + Listing.objects.filter( + created__gte=time_period["start"], bids__isnull=False + ) + .distinct() + .count() + ) + + conversion_rate = ( + (listings_with_bids / total_listings * 100) if total_listings > 0 else 0 + ) + + # Métricas de engagement + total_users = User.objects.filter(date_joined__gte=time_period["start"]).count() + active_users = ( + User.objects.filter( + Q(listings__created__gte=time_period["start"]) + | Q(bids__listing__created__gte=time_period["start"]) + | Q(comments__created__gte=time_period["start"]) + ) + .distinct() + .count() + ) + + user_engagement_rate = ( + (active_users / total_users * 100) if total_users > 0 else 0 + ) + + # Métricas de retención + returning_users = ( + User.objects.filter( + Q(listings__created__gte=time_period["start"]) + | Q(bids__listing__created__gte=time_period["start"]) + ) + .annotate(activity_count=Count("listings") + Count("bids")) + .filter(activity_count__gt=1) + .count() + ) + + retention_rate = ( + (returning_users / active_users * 100) if active_users > 0 else 0 + ) + + return { + "conversion_rate": round(conversion_rate, 2), + "user_engagement_rate": round(user_engagement_rate, 2), + "retention_rate": round(retention_rate, 2), + "total_listings": total_listings, + "active_users": active_users, + "returning_users": returning_users, + } + + +class AlertSystem: + """ + Sistema de alertas para el dashboard + """ + + @staticmethod + def check_low_activity_alert(): + """ + Verificar alerta de baja actividad + """ + threshold_days = 7 + cutoff_date = timezone.now() - timedelta(days=threshold_days) + + recent_listings = Listing.objects.filter(created__gte=cutoff_date).count() + recent_bids = Bid.objects.filter(listing__created__gte=cutoff_date).count() + + if recent_listings < 5 or recent_bids < 10: + return { + "type": "warning", + "message": f"Baja actividad detectada: {recent_listings} subastas, {recent_bids} pujas en los últimos {threshold_days} días", + "severity": "medium", + } + + return None + + @staticmethod + def check_high_value_alert(): + """ + Verificar alerta de pujas muy altas + """ + high_value_bids = Bid.objects.filter(amount__gt=10000).count() + + if high_value_bids > 0: + return { + "type": "info", + "message": f"{high_value_bids} pujas de alto valor (>$10,000) detectadas", + "severity": "low", + } + + return None + + @staticmethod + def get_all_alerts(): + """ + Obtener todas las alertas activas + """ + alerts = [] + + low_activity = AlertSystem.check_low_activity_alert() + if low_activity: + alerts.append(low_activity) + + high_value = AlertSystem.check_high_value_alert() + if high_value: + alerts.append(high_value) + + return alerts diff --git a/auctions/error_views.py b/auctions/error_views.py new file mode 100644 index 0000000..30d408f --- /dev/null +++ b/auctions/error_views.py @@ -0,0 +1,192 @@ +""" +Vistas personalizadas para manejo de errores +Incluye funcionalidad de debug en desarrollo y páginas personalizadas en producción +""" + +from django.shortcuts import render +from django.http import HttpResponse +from django.conf import settings +from django.views.decorators.http import require_http_methods +import traceback +import sys + + +def custom_404_view(request, exception=None): + """ + Vista personalizada para error 404 + Muestra información de debug en desarrollo y página personalizada en producción + """ + context = { + "debug": settings.DEBUG, + "request_path": request.path, + "request_method": request.method, + } + + # En modo desarrollo, agregar información técnica + if settings.DEBUG: + debug_info = f""" +Request Method: {request.method} +Request URL: {request.build_absolute_uri()} +Raised by: {getattr(exception, "__class__", "Unknown")} + +Using the URLconf defined in {settings.ROOT_URLCONF}, Django tried these URL patterns: + +{_get_url_patterns_info()} + +The current path, {request.path}, matched the last one. + +You're seeing this error because you have DEBUG = True in your Django settings file. +Change that to False, and Django will display a standard 404 page. + """ + context["debug_info"] = debug_info.strip() + + return render(request, "auctions/errors/404.html", context, status=404) + + +def custom_500_view(request): + """ + Vista personalizada para error 500 + Muestra información de debug en desarrollo y página personalizada en producción + """ + context = { + "debug": settings.DEBUG, + "request_path": request.path, + "request_method": request.method, + } + + # En modo desarrollo, agregar información técnica + if settings.DEBUG: + exc_type, exc_value, exc_traceback = sys.exc_info() + if exc_traceback: + debug_info = f""" +Request Method: {request.method} +Request URL: {request.build_absolute_uri()} +Exception Type: {exc_type.__name__ if exc_type else "Unknown"} +Exception Value: {str(exc_value) if exc_value else "Unknown"} + +Traceback: +{traceback.format_exc()} + +You're seeing this error because you have DEBUG = True in your Django settings file. + """ + context["debug_info"] = debug_info.strip() + + return render(request, "auctions/errors/500.html", context, status=500) + + +def custom_403_view(request, exception=None): + """ + Vista personalizada para error 403 (Forbidden) + """ + context = { + "debug": settings.DEBUG, + "request_path": request.path, + "request_method": request.method, + } + + if settings.DEBUG: + debug_info = f""" +Request Method: {request.method} +Request URL: {request.build_absolute_uri()} +Exception: {getattr(exception, "__class__", "Unknown")} + +You don't have permission to access this resource. + """ + context["debug_info"] = debug_info.strip() + + return render(request, "auctions/errors/403.html", context, status=403) + + +def custom_400_view(request, exception=None): + """ + Vista personalizada para error 400 (Bad Request) + """ + context = { + "debug": settings.DEBUG, + "request_path": request.path, + "request_method": request.method, + } + + if settings.DEBUG: + debug_info = f""" +Request Method: {request.method} +Request URL: {request.build_absolute_uri()} +Exception: {getattr(exception, "__class__", "Unknown")} + +Bad Request - The request could not be understood by the server. + """ + context["debug_info"] = debug_info.strip() + + return render(request, "auctions/errors/400.html", context, status=400) + + +def _get_url_patterns_info(): + """ + Obtener información sobre los patrones de URL disponibles + """ + try: + from django.urls import get_resolver + + resolver = get_resolver() + patterns = [] + + def extract_patterns(url_patterns, prefix=""): + for pattern in url_patterns: + if hasattr(pattern, "url_patterns"): + # Es un include + extract_patterns( + pattern.url_patterns, prefix + str(pattern.pattern) + ) + else: + # Es un patrón de URL + patterns.append( + f"{prefix}{pattern.pattern} [{getattr(pattern, 'name', 'No name')}]" + ) + + extract_patterns(resolver.url_patterns) + return "\n".join(patterns) + except Exception: + return "No se pudieron obtener los patrones de URL" + + +@require_http_methods(["GET"]) +def test_404_view(request): + """ + Vista para probar el error 404 (solo en desarrollo) + """ + if not settings.DEBUG: + return HttpResponse( + "Esta vista solo está disponible en modo desarrollo", status=404 + ) + + from django.http import Http404 + + raise Http404("Esta es una página de prueba para el error 404") + + +@require_http_methods(["GET"]) +def test_500_view(request): + """ + Vista para probar el error 500 (solo en desarrollo) + """ + if not settings.DEBUG: + return HttpResponse( + "Esta vista solo está disponible en modo desarrollo", status=404 + ) + + raise Exception("Esta es una excepción de prueba para el error 500") + + +@require_http_methods(["GET"]) +def test_403_view(request): + """ + Vista para probar el error 403 (solo en desarrollo) + """ + if not settings.DEBUG: + return HttpResponse( + "Esta vista solo está disponible en modo desarrollo", status=404 + ) + + from django.core.exceptions import PermissionDenied + + raise PermissionDenied("Esta es una excepción de prueba para el error 403") diff --git a/auctions/management/__init__.py b/auctions/management/__init__.py new file mode 100644 index 0000000..a94f2a3 --- /dev/null +++ b/auctions/management/__init__.py @@ -0,0 +1 @@ +# Management package diff --git a/auctions/management/commands/__init__.py b/auctions/management/commands/__init__.py new file mode 100644 index 0000000..b5a3a84 --- /dev/null +++ b/auctions/management/commands/__init__.py @@ -0,0 +1 @@ +# Commands package diff --git a/auctions/management/commands/generate_reports.py b/auctions/management/commands/generate_reports.py new file mode 100644 index 0000000..f09022c --- /dev/null +++ b/auctions/management/commands/generate_reports.py @@ -0,0 +1,119 @@ +""" +Comando de Django para generar reportes automáticos +""" + +from django.core.management.base import BaseCommand +from django.utils import timezone +from auctions.data_utils import ReportGenerator, AlertSystem +import json +import os + + +class Command(BaseCommand): + help = "Genera reportes automáticos del sistema de subastas" + + def add_arguments(self, parser): + parser.add_argument( + "--days", + type=int, + default=30, + help="Número de días para el análisis (default: 30)", + ) + parser.add_argument( + "--output-dir", + type=str, + default="reports", + help="Directorio de salida para los reportes (default: reports)", + ) + parser.add_argument( + "--format", + type=str, + choices=["json", "csv"], + default="json", + help="Formato de salida (default: json)", + ) + + def handle(self, *args, **options): + days = options["days"] + output_dir = options["output_dir"] + output_format = options["format"] + + # Crear directorio de salida si no existe + os.makedirs(output_dir, exist_ok=True) + + self.stdout.write(f"Generando reportes para los últimos {days} días...") + + # Generar reporte de actividad de usuarios + self.stdout.write("Generando reporte de actividad de usuarios...") + user_report = ReportGenerator.generate_user_activity_report(days=days) + + # Generar análisis del mercado + self.stdout.write("Generando análisis del mercado...") + market_analysis = ReportGenerator.generate_market_analysis(days=days) + + # Generar métricas de rendimiento + self.stdout.write("Generando métricas de rendimiento...") + performance_metrics = ReportGenerator.generate_performance_metrics(days=days) + + # Obtener alertas + self.stdout.write("Verificando alertas...") + alerts = AlertSystem.get_all_alerts() + + # Consolidar reporte + report_data = { + "generated_at": timezone.now().isoformat(), + "period_days": days, + "user_activity": user_report, + "market_analysis": market_analysis, + "performance_metrics": performance_metrics, + "alerts": alerts, + } + + # Guardar reporte + timestamp = timezone.now().strftime("%Y%m%d_%H%M%S") + + if output_format == "json": + filename = f"report_{timestamp}.json" + filepath = os.path.join(output_dir, filename) + + with open(filepath, "w", encoding="utf-8") as f: + json.dump(report_data, f, indent=2, ensure_ascii=False, default=str) + + elif output_format == "csv": + # Generar archivos CSV separados + import pandas as pd + + # Reporte de usuarios + user_filename = f"user_activity_{timestamp}.csv" + user_filepath = os.path.join(output_dir, user_filename) + pd.DataFrame(user_report).to_csv(user_filepath, index=False) + + # Análisis del mercado + market_filename = f"market_analysis_{timestamp}.csv" + market_filepath = os.path.join(output_dir, market_filename) + market_df = pd.DataFrame([market_analysis]) + market_df.to_csv(market_filepath, index=False) + + # Métricas de rendimiento + metrics_filename = f"performance_metrics_{timestamp}.csv" + metrics_filepath = os.path.join(output_dir, metrics_filename) + pd.DataFrame([performance_metrics]).to_csv(metrics_filepath, index=False) + + self.stdout.write( + self.style.SUCCESS(f"Reportes generados exitosamente en {output_dir}/") + ) + + # Mostrar resumen + self.stdout.write("\n--- RESUMEN DEL REPORTE ---") + self.stdout.write(f"Período analizado: {days} días") + self.stdout.write(f"Usuarios analizados: {len(user_report)}") + self.stdout.write(f"Subastas totales: {market_analysis['total_listings']}") + self.stdout.write( + f"Tasa de conversión: {performance_metrics['conversion_rate']}%" + ) + self.stdout.write(f"Alertas activas: {len(alerts)}") + + if alerts: + self.stdout.write("\n--- ALERTAS ---") + for alert in alerts: + self.stdout.write(f"- {alert['message']} ({alert['severity']})") diff --git a/auctions/middleware.py b/auctions/middleware.py new file mode 100644 index 0000000..12cd9a9 --- /dev/null +++ b/auctions/middleware.py @@ -0,0 +1,90 @@ +""" +Middleware personalizado para manejo de errores +Intercepta errores 404 y usa nuestros handlers personalizados +""" + +from django.http import HttpResponseNotFound +from django.conf import settings +from django.urls import resolve, Resolver404 +from django.core.exceptions import PermissionDenied +from django.shortcuts import render +import traceback +import sys + + +class CustomErrorHandlerMiddleware: + """ + Middleware que intercepta errores y usa nuestros handlers personalizados + """ + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + response = self.get_response(request) + return response + + def process_exception(self, request, exception): + """ + Procesar excepciones y usar nuestros handlers personalizados + """ + if ( + hasattr(settings, "USE_CUSTOM_ERROR_HANDLERS") + and settings.USE_CUSTOM_ERROR_HANDLERS + ): + if isinstance(exception, Resolver404): + return self._handle_404(request, exception) + elif isinstance(exception, PermissionDenied): + return self._handle_403(request, exception) + else: + return self._handle_500(request, exception) + return None + + def _handle_404(self, request, exception): + """ + Manejar error 404 con nuestro handler personalizado + """ + from .error_views import custom_404_view + + return custom_404_view(request, exception) + + def _handle_403(self, request, exception): + """ + Manejar error 403 con nuestro handler personalizado + """ + from .error_views import custom_403_view + + return custom_403_view(request, exception) + + def _handle_500(self, request, exception): + """ + Manejar error 500 con nuestro handler personalizado + """ + from .error_views import custom_500_view + + return custom_500_view(request, exception) + + +class Custom404Middleware: + """ + Middleware específico para interceptar errores 404 + """ + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + response = self.get_response(request) + + # Si la respuesta es 404 y tenemos handlers personalizados habilitados + if ( + response.status_code == 404 + and hasattr(settings, "USE_CUSTOM_ERROR_HANDLERS") + and settings.USE_CUSTOM_ERROR_HANDLERS + ): + # Usar nuestro handler personalizado + from .error_views import custom_404_view + + return custom_404_view(request, None) + + return response diff --git a/auctions/models.py b/auctions/models.py index 28ba1d6..185edf9 100644 --- a/auctions/models.py +++ b/auctions/models.py @@ -75,3 +75,7 @@ class Watchlist(models.Model): def __str__(self): return f"{self.user} added {self.listing.title} to watchlist" + + def toggle(self): + self.active = not self.active + self.save() diff --git a/auctions/static/css/admin.css b/auctions/static/css/admin.css new file mode 100644 index 0000000..5309f27 --- /dev/null +++ b/auctions/static/css/admin.css @@ -0,0 +1,213 @@ +body { + background-color: var(--bg-secondary); + font-family: var(--font-primary); +} + +.admin-sidebar { + min-height: 100vh; + background: var(--nav-gradient); + box-shadow: var(--shadow-md); +} + +.admin-sidebar .nav-link { + color: rgba(255,255,255,0.8); + padding: var(--space-3) var(--space-5); + border-radius: var(--radius-lg); + margin: 2px 0; + transition: var(--transition-all); +} + +.admin-sidebar .nav-link:hover, +.admin-sidebar .nav-link.active { + color: var(--white); + background: rgba(255,255,255,0.2); + transform: translateX(5px); +} + +.admin-sidebar .nav-link i { + width: 20px; + margin-right: var(--space-3); +} + +.admin-header { + background: var(--bg-primary); + box-shadow: var(--shadow-lg); + padding: var(--space-4) var(--space-8); + margin-bottom: var(--space-8); +} + +.admin-content { + padding: 0 var(--space-8) var(--space-8); +} + +.metric-card { + background: var(--bg-primary); + border-radius: var(--radius-2xl); + padding: var(--space-6); + box-shadow: var(--shadow-lg); + border: none; + transition: transform 0.3s ease, box-shadow 0.3s ease; + margin-bottom: var(--space-5); +} + +.metric-card:hover { + transform: translateY(-5px); + box-shadow: var(--shadow-xl); +} + +.metric-icon { + width: 60px; + height: 60px; + border-radius: var(--radius-full); + display: flex; + align-items: center; + justify-content: center; + font-size: var(--text-2xl); + color: var(--white); + margin-bottom: var(--space-4); +} + +.metric-value { + font-size: var(--text-4xl); + font-weight: var(--font-bold); + color: var(--text-primary); + margin-bottom: var(--space-2); +} + +.metric-label { + color: var(--text-secondary); + font-size: var(--text-sm); + text-transform: uppercase; + letter-spacing: 1px; +} + +.chart-container { + background: var(--bg-primary); + border-radius: var(--radius-2xl); + padding: var(--space-6); + box-shadow: var(--shadow-lg); + margin-bottom: var(--space-8); +} + +.table-container { + background: var(--bg-primary); + border-radius: var(--radius-2xl); + padding: var(--space-6); + box-shadow: var(--shadow-lg); + overflow-x: auto; +} + +.table th { + border: none; + background: var(--bg-secondary); + color: var(--text-secondary); + font-weight: var(--font-semibold); + text-transform: uppercase; + font-size: var(--text-xs); + letter-spacing: 1px; +} + +.table td { + border: none; + vertical-align: middle; + padding: var(--space-4); +} + +.table tbody tr { + border-bottom: 1px solid var(--border-light); +} + +.table tbody tr:hover { + background: var(--bg-secondary); +} + +.badge-custom { + padding: var(--space-2) var(--space-4); + border-radius: var(--radius-full); + font-size: var(--text-xs); + font-weight: var(--font-medium); +} + +.btn-custom { + border-radius: var(--radius-lg); + padding: var(--space-2) var(--space-5); + font-weight: var(--font-medium); + transition: var(--transition-all); +} + +.btn-custom:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-lg); +} + +.alert-custom { + border: none; + border-radius: var(--radius-lg); + padding: var(--space-4) var(--space-5); + margin-bottom: var(--space-5); +} + +.loading { + display: none; + text-align: center; + padding: var(--space-5); +} + +.spinner-border-custom { + width: 3rem; + height: 3rem; + color: var(--primary-500); +} + +/* Utility classes */ +.icon-sm { + width: 30px; + height: 30px; +} + +.icon-md { + width: 35px; + height: 35px; +} + +.icon-lg { + width: 45px; + height: 45px; +} + +.img-cover { + object-fit: cover; +} + +.img-cover-sm { + width: 40px; + height: 40px; + object-fit: cover; +} + +/* Gradients for metric icons */ +.metric-icon-gradient-1 { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } +.metric-icon-gradient-2 { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); } +.metric-icon-gradient-3 { background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); } +.metric-icon-gradient-4 { background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); } +.metric-icon-gradient-5 { background: linear-gradient(135deg, #fa709a 0%, #fee140 100%); } +.metric-icon-gradient-6 { background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); } +.metric-icon-gradient-7 { background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); } +.metric-icon-gradient-8 { background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%); } + + +/* Responsive adjustments */ +@media (max-width: 768px) { + .admin-sidebar { + min-height: auto; + } + + .admin-content { + padding: 0 var(--space-4) var(--space-4); + } + + .admin-header { + padding: var(--space-3) var(--space-4); + margin-bottom: var(--space-5); + } +} diff --git a/auctions/static/css/auctions/styles.css b/auctions/static/css/auctions/styles.css index c081b06..83327cb 100644 --- a/auctions/static/css/auctions/styles.css +++ b/auctions/static/css/auctions/styles.css @@ -1,427 +1,235 @@ -:root { - /* Light Mode Colors - Paleta de subastas premium */ - --primary-color: #0f5bab; /* Azul confiable */ - --primary-dark: #053263; /* Azul oscuro para confianza institucional */ - --primary-light: #e3f0ff; /* Azul muy claro para fondos */ - --secondary-color: #e94e24; /* Naranja-rojo atención */ - --secondary-dark: #bb3a14; /* Naranja-rojo oscuro */ - --secondary-light: #fff0ed; /* Tono claro naranja */ - - /* Colores específicos para subastas */ - --price-color: #16a34f; /* Verde brillante para precios */ - --bid-color: #d97706; /* Naranja ámbar para pujas */ - --timer-color: #c2410c; /* Naranja rojizo para temporizadores */ - --premium-item: #854d0e; /* Ámbar oscuro para items destacados */ - - /* Background Colors - Light */ - --bg-main: rgba(255, 255, 255, 0.98); - --bg-main-form: #f8fafc; - --bg-secondary: rgba(240, 242, 245, 0.7); - --bg-accent: #053263; - --bg-hover: rgba(15, 91, 171, 0.08); - --bg-user: rgba(15, 91, 171, 0.15); - --card-bg: rgba(255, 255, 255, 0.95); - --card-top: rgba(0, 0, 0, 0.05); - --gradient-primary: linear-gradient(135deg, #0f5bab, #0284c7); - --border-color: #d1d5db; - --bg-secondary-form: #f9fafb; - --bg-premium: linear-gradient(135deg, #fbbf24, #f97316); - --bg-timer: linear-gradient(135deg, #f87171, #ef4444); - - /* Text Colors - Light */ - --text-primary: #111827; /* Casi negro para mejor legibilidad */ - --text-secondary: #374151; /* Gris oscuro */ - --text-muted: #6b7280; /* Gris medio */ - --text-light: #ffffff; /* Blanco */ - --price-icon: #059669; /* Verde para iconos de precio */ - - /* Status Colors - comunicación clara en subastas */ - --success-color: #16a34f; /* Verde brillante */ - --warning-color: #eab308; /* Amarillo ámbar */ - --danger-color: #dc2626; /* Rojo vivo */ - --info-color: #0284c7; /* Azul cielo */ - --toggle-icon: #f59e0b; /* Ámbar */ - - /* Badges y etiquetas específicas de subastas */ - --badge-new: #3b82f6; /* Azul para items nuevos */ - --badge-ending: #ef4444; /* Rojo para subastas por terminar */ - --badge-popular: #8b5cf6; /* Violeta para items populares */ - --badge-premium: linear-gradient( - 45deg, - #f59e0b, - #d97706 - ); /* Dorado para premium */ - - /* Shadow - más refinada */ - --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.08); - --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.05); - --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.03); - --card-shadow: rgba(0, 0, 0, 0.06); - --hover-shadow: 0 10px 25px rgba(15, 91, 171, 0.15); - --border-light: #f3f4f6; -} - -/* Dark Mode Colors - Contraste optimizado para subastas */ -[data-theme='dark'] { - /* Background Colors - Dark */ - --bg-main: rgba(22, 27, 34, 0.98); /* Azul-gris muy oscuro */ - --bg-main-form: #374151; /* Gris oscuro */ - --bg-secondary: rgba(31, 41, 55, 0.7); /* Gris azulado oscuro */ - --bg-accent: #e3f0ff; /* Contraste invertido */ - --bg-hover: rgba(59, 130, 246, 0.15); /* Azul brillante hover */ - --bg-user: rgba(59, 130, 246, 0.2); /* Azul brillante para usuario */ - --card-bg: rgba(30, 41, 59, 0.95); /* Azul oscuro para tarjetas */ - --bg-secondary-form: #1e293b; /* Azul oscuro para formularios */ - --card-top: rgba(255, 255, 255, 0.05); - - /* Text Colors - Dark con contraste mejorado */ - --text-primary: #f8fafc; /* Blanco con tinte azul */ - --text-secondary: #e2e8f0; /* Gris muy claro */ - --text-muted: #cbd5e1; /* Gris claro medio */ - --price-color: #4ade80; /* Verde más brillante */ - --price-icon: #34d399; /* Verde turquesa */ - - /* Colores específicos para subastas - modo oscuro */ - --bid-color: #fb923c; /* Naranja más brillante */ - --timer-color: #fb7185; /* Rojo rosado */ - --premium-item: #fcd34d; /* Amarillo ámbar claro */ - - /* Border & Shadow - Dark */ - --border-color: #334155; - --border-light: #1e293b; - --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3); - --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.4); - --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.4); - --card-shadow: rgba(0, 0, 0, 0.5); - --hover-shadow: 0 10px 25px rgba(59, 130, 246, 0.25); - - /* Status Colors - Dark con mejor visibilidad */ - --success-color: #22c55e; /* Verde más brillante */ - --warning-color: #fbbf24; /* Amarillo más brillante */ - --danger-color: #ef4444; /* Rojo más brillante */ - --info-color: #38bdf8; /* Azul brillante */ - --primary-dark: #60a5fa; /* Azul más claro */ - - /* Gradientes mejorados para modo oscuro */ - --gradient-primary: linear-gradient(135deg, #3b82f6, #1d4ed8); - --gradient-secondary: linear-gradient(135deg, #f43f5e, #881337); - --bg-premium: linear-gradient(135deg, #fbbf24, #d97706); - --bg-timer: linear-gradient(135deg, #f87171, #dc2626); - --toggle-icon: #fde68a; /* Amarillo pálido */ -} - -/* Base Styles */ -body { - min-height: 100vh; - display: flex; - flex-direction: column; - color: var(--text-primary); - background: var(--bg-main); - background-size: cover; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, - Helvetica, Arial, sans-serif; -} - -.container { - width: 100%; - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto; -} - -.bid-now-btn { - background: var(--bid-color); - color: white; - font-weight: 600; - border: none; - padding: 0.75rem 1.5rem; - border-radius: 8px; - transition: all 0.3s ease; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); -} - -.bid-now-btn:hover { - transform: translateY(-2px); - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); -} - -.auction-timer { - color: var(--timer-color); - font-weight: 700; - display: flex; - align-items: center; - gap: 0.5rem; -} - -.auction-timer i { - animation: pulse 1.5s infinite; -} - -.badge-ending-soon { - background: var(--badge-ending); - color: white; - padding: 0.25rem 0.75rem; - border-radius: 100px; - font-size: 0.8rem; - font-weight: 600; - animation: pulse 2s infinite; -} - -.badge-premium { - background: var(--badge-premium); - color: white; - padding: 0.25rem 0.75rem; - border-radius: 100px; - font-size: 0.8rem; - font-weight: 600; -} - -/* Common Elements */ -.btn-primary { - background: var(--gradient-primary); - border: none; - transition: all 0.3s ease; - position: relative; - overflow: hidden; - box-shadow: var(--shadow-sm); - font-weight: 600; - padding: 0.75rem 1.5rem; - border-radius: 8px; -} - -.btn-primary:hover { - transform: translateY(-2px); - box-shadow: var(--hover-shadow); -} - -.btn-primary:active { - transform: translateY(1px); - box-shadow: var(--shadow-sm); -} - -[data-theme='dark'] .btn-primary { - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); -} - -[data-theme='dark'] .btn-primary:hover { - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4); - background: linear-gradient( - 45deg, - var(--primary-dark), - var(--primary-color) - ); -} - -.btn-secondary { - background: var(--gradient-secondary); - border: none; - color: var(--text-light); -} - -.alert-success { - background: var(--success-color); - color: var(--text-light); -} - -.alert-warning { - background: var(--warning-color); - color: var(--text-primary); -} - -.alert-danger { - background: var(--danger-color); - color: var(--text-light); -} - -.alert-info { - background: var(--info-color); - color: var(--text-light); -} - -/* Main Content Area */ -.py-4 { - padding-top: 2rem !important; - padding-bottom: 2rem !important; -} - -/* Skip link para accesibilidad */ -.skip-link { - position: absolute; - top: -40px; - left: 0; - background: var(--primary-color); - color: white; - padding: 8px; - z-index: 9999; - transition: top 0.3s; -} - -.skip-link:focus { - top: 0; -} - -/* Botón Back to Top */ -.back-to-top { - position: fixed; - bottom: 20px; - right: 20px; - width: 50px; - height: 50px; - border-radius: 50%; - background: var(--primary-color); - color: white; - border: none; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); - display: none; - z-index: 99; - transition: all 0.3s ease; -} - -.back-to-top:hover { - transform: translateY(-3px); - box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); -} - -@media (max-width: 768px) { - .container { - max-width: 720px; - padding: 0 1rem; - } - - .py-4 { - padding-top: 1.5rem !important; - padding-bottom: 1.5rem !important; - } - - .display-4 { - font-size: 2rem; - } - - .lead { - font-size: 1rem; - } - - .row { - margin-right: -8px; - margin-left: -8px; - } - - .col, - [class*='col-'] { - padding-right: 8px; - padding-left: 8px; - } - - .price-tag { - font-size: 1.25rem; - } - - .auction-timer { - font-size: 0.9rem; - } - - .badge-ending-soon, - .badge-premium { - font-size: 0.7rem; - padding: 0.2rem 0.5rem; - } -} - -@media (max-width: 576px) { - .container { - max-width: 100%; - padding: 0 0.75rem; - } - - .py-4 { - padding-top: 1rem !important; - padding-bottom: 1rem !important; - } - - .display-4 { - font-size: 1.75rem; - } - - .row { - margin-right: -5px; - margin-left: -5px; - } - - .col, - [class*='col-'] { - padding-right: 5px; - padding-left: 5px; - } - - /* Grid system adjustments */ - .row-cols-1 > * { - flex: 0 0 100%; - max-width: 100%; - } - - .nav-link span { - font-size: 0.9rem; - } - - .back-to-top { - width: 40px; - height: 40px; - bottom: 15px; - right: 15px; - } -} - -/* Landscape Mode */ -@media (max-height: 500px) and (orientation: landscape) { - .container { - padding-top: 0.5rem; - padding-bottom: 0.5rem; - } - - .py-4 { - padding-top: 0.5rem !important; - padding-bottom: 0.5rem !important; - } -} - -/* Dark Mode Adjustments */ -@media (max-width: 768px) { - [data-theme='dark'] .container { - background: var(--bg-main); - } -} - -/* Animations */ -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(-10px); - } - - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes pulse { - 0% { - opacity: 1; - } - 50% { - opacity: 0.7; - } - 100% { - opacity: 1; - } -} - -@keyframes bidHighlight { - 0% { - background-color: var(--bid-color); - } - 100% { - background-color: transparent; - } -} +/* Base Styles */ +body { + min-height: 100vh; + display: flex; + flex-direction: column; + color: var(--text-primary); + background: var(--bg-primary); /* Mapped from --bg-main */ + background-size: cover; + font-family: var(--font-primary); /* Use font from variables.css */ +} + +.bid-now-btn { + background: var(--bid-current); /* Mapped from --bid-color */ + color: var(--white); + font-weight: var(--font-semibold); + border: none; + padding: var(--space-3) var(--space-6); + border-radius: var(--radius-lg); + transition: var(--transition-all); + box-shadow: var(--shadow-md); +} + +.bid-now-btn:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-lg); +} + +.auction-timer { + color: var(--status-pending); /* Mapped from --timer-color */ + font-weight: var(--font-bold); + display: flex; + align-items: center; + gap: var(--space-2); +} + +.auction-timer i { + animation: pulse 1.5s infinite; +} + +.badge-ending-soon { + background: var(--status-cancelled); /* Mapped from --badge-ending */ + color: var(--white); + padding: var(--space-1) var(--space-3); + border-radius: var(--radius-full); + font-size: var(--text-sm); + font-weight: var(--font-semibold); + animation: pulse 2s infinite; +} + +.badge-premium { + background: var(--nav-gradient); /* Mapped from --badge-premium */ + color: var(--white); + padding: var(--space-1) var(--space-3); + border-radius: var(--radius-full); + font-size: var(--text-sm); + font-weight: var(--font-semibold); +} + +/* Common Elements */ +/* Overriding .btn-primary to use our design system gradients and shadows */ +.btn-primary { + background: var(--nav-gradient); /* Mapped from --gradient-primary */ + border: none; + transition: var(--transition-all); + position: relative; + overflow: hidden; + box-shadow: var(--shadow-sm); + font-weight: var(--font-semibold); + padding: var(--space-3) var(--space-6); + border-radius: var(--radius-lg); + color: var(--white); +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-lg); /* Mapped from --hover-shadow */ +} + +.btn-primary:active { + transform: translateY(1px); + box-shadow: var(--shadow-sm); +} + +[data-theme='dark'] .btn-primary { + background: var(--nav-gradient-dark); + box-shadow: var(--shadow-sm); +} + +[data-theme='dark'] .btn-primary:hover { + box-shadow: var(--shadow-lg); +} + +/* .btn-secondary is not a standard Bootstrap class, so we can define it */ +.btn-secondary { + background: var(--secondary-500); + border: none; + color: var(--white); +} + +[data-theme='dark'] .btn-secondary { + background: var(--secondary-700); +} + + +/* We will rely on Bootstrap for alerts, but we can add our variables if needed */ +/* The existing alert-* classes in bootstrap should work well with our color scheme. + If specific overrides are needed, they can be added here. For now, removing them + to rely on the default bootstrap behavior. */ + + +/* Main Content Area */ +/* py-4 is a bootstrap class. We should avoid overriding it. + If more padding is needed, a custom class should be used. + Removing the override for now. */ + +/* Skip link para accesibilidad */ +.skip-link { + position: absolute; + top: -40px; + left: 0; + background: var(--primary-500); /* Mapped from --primary-color */ + color: white; + padding: var(--space-2); + z-index: var(--z-fixed); + transition: top 0.3s; +} + +.skip-link:focus { + top: 0; +} + +/* Botón Back to Top */ +.back-to-top { + position: fixed; + bottom: 20px; + right: 20px; + width: 50px; + height: 50px; + border-radius: var(--radius-full); + background: var(--primary-500); /* Mapped from --primary-color */ + color: var(--white); + border: none; + box-shadow: var(--shadow-md); + display: none; + z-index: var(--z-sticky); + transition: var(--transition-all); +} + +.back-to-top:hover { + transform: translateY(-3px); + box-shadow: var(--shadow-lg); +} + +/* Media queries remain largely the same, but we can check for variable usage inside them */ + +@media (max-width: 768px) { + /* The overrides for .py-4, .display-4, .lead, .row, .col were removed + as it's better to use Bootstrap's responsive classes directly in the HTML. + If these specific overrides are essential, they should be reviewed. */ + + .price-tag { + font-size: var(--text-xl); + } + + .auction-timer { + font-size: var(--text-sm); + } + + .badge-ending-soon, + .badge-premium { + font-size: var(--text-xs); + padding: 0.2rem 0.5rem; + } +} + +@media (max-width: 576px) { + /* More overrides removed, rely on Bootstrap */ + + .nav-link span { + font-size: var(--text-sm); + } + + .back-to-top { + width: 40px; + height: 40px; + bottom: 15px; + right: 15px; + } +} + +/* Landscape Mode */ +@media (max-height: 500px) and (orientation: landscape) { + /* No variables to replace here */ +} + +/* Dark Mode Adjustments */ +@media (max-width: 768px) { + [data-theme='dark'] .container { + background: var(--bg-primary); /* Mapped from --bg-main */ + } +} + +/* Animations */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-10px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes pulse { + 0% { + opacity: 1; + } + 50% { + opacity: 0.7; + } + 100% { + opacity: 1; + } +} + +@keyframes bidHighlight { + 0% { + background-color: var(--bid-current); /* Mapped from --bid-color */ + } + 100% { + background-color: transparent; + } +} diff --git a/auctions/static/css/components.css b/auctions/static/css/components.css index 8497ea6..1dcf1cd 100644 --- a/auctions/static/css/components.css +++ b/auctions/static/css/components.css @@ -1,3 +1,10 @@ +/* ======================================== + COMPONENTS STYLES + ======================================== */ + +/* Import Variables */ +@import url('../css/variables.css'); + /* Alert Component Styles */ .alert-container { max-width: 400px; @@ -257,7 +264,6 @@ .auction-card .description-text { color: var(--text-secondary); display: -webkit-box; - -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.5; @@ -319,7 +325,6 @@ } .auction-card .description-text { - -webkit-line-clamp: 2; font-size: 0.9rem; } @@ -717,7 +722,7 @@ .nav-link { color: var(--text-secondary); font-weight: 600; - padding: 0.6rem 1.2rem; + padding: 0.2rem 0.4rem; border-radius: 8px; transition: all 0.3s ease; display: flex; diff --git a/auctions/static/css/components/alert.css b/auctions/static/css/components/alert.css index 2616719..1e8e628 100644 --- a/auctions/static/css/components/alert.css +++ b/auctions/static/css/components/alert.css @@ -1,210 +1,199 @@ -/* Alert Component Styles */ +/* ================================= + ALERT COMPONENT STYLES + ================================= */ + .alert-container { - max-width: 400px; - min-width: 300px; - top: 70px; - right: 0; + max-width: 400px; + min-width: 300px; + top: 70px; + right: 0; + position: fixed; + z-index: var(--z-toast); } .alert-wrapper { - animation: slideInRight 0.4s ease-out forwards; - position: relative; + animation: slideInRight 0.4s ease-out forwards; + position: relative; } .custom-alert { - border: none; - border-radius: 12px; - padding: 1rem 1.25rem; - box-shadow: var(--shadow-md); - margin-bottom: 0.75rem; - font-size: 0.95rem; - position: relative; - overflow: hidden; - font-weight: 500; + border: none; + border-radius: var(--radius-xl); + padding: var(--space-4) var(--space-5); + box-shadow: var(--shadow-md); + margin-bottom: var(--space-3); + font-size: var(--text-base); + position: relative; + overflow: hidden; + font-weight: var(--font-medium); + display: flex; + align-items: center; } /* Alert status bar */ .custom-alert::before { - content: ''; - position: absolute; - left: 0; - top: 0; - bottom: 0; - width: 4px; - border-radius: 4px 0 0 4px; + content: ''; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 4px; + border-radius: 4px 0 0 4px; } /* Alert content */ .alert-content { - flex: 1; - margin: 0 1rem; - line-height: 1.4; - overflow-wrap: break-word; - word-wrap: break-word; - hyphens: auto; + flex: 1; + margin: 0 var(--space-4); + line-height: var(--leading-normal); + overflow-wrap: break-word; + word-wrap: break-word; + hyphens: auto; } /* Alert icon */ .alert-icon { - font-size: 1.5rem; - display: flex; - align-items: center; + font-size: 1.5rem; + display: flex; + align-items: center; } /* Alert close button */ .custom-alert .btn-close { - opacity: 0.7; - font-size: 0.875rem; - transition: all 0.2s ease; + opacity: 0.7; + font-size: var(--text-sm); + transition: var(--transition-all); } .custom-alert .btn-close:hover { - opacity: 1; - transform: scale(1.1); + opacity: 1; + transform: scale(1.1); } /* Progress bar animation for auto-dismiss */ .custom-alert::after { - content: ''; - position: absolute; - bottom: 0; - left: 0; - height: 3px; - width: 100%; - opacity: 0.5; - animation: progress 5s linear forwards; + content: ''; + position: absolute; + bottom: 0; + left: 0; + height: 3px; + width: 100%; + opacity: 0.5; + animation: progress 5s linear forwards; } /* Alert types - Success */ .alert-success { - background-color: rgba(34, 197, 94, 0.1); - color: var(--text-primary); + background-color: rgba(from var(--success) r g b / 0.1); + color: var(--text-primary); } .alert-success::before, .alert-success::after { - background-color: var(--success-color); + background-color: var(--success); } .alert-success .alert-icon { - color: var(--success-color); + color: var(--success); } /* Alert types - Danger */ .alert-danger { - background-color: rgba(239, 68, 68, 0.1); - color: var(--text-primary); + background-color: rgba(from var(--error) r g b / 0.1); + color: var(--text-primary); } .alert-danger::before, .alert-danger::after { - background-color: var(--danger-color); + background-color: var(--error); } .alert-danger .alert-icon { - color: var(--danger-color); + color: var(--error); } /* Alert types - Warning */ .alert-warning { - background-color: rgba(251, 191, 36, 0.1); - color: var(--text-primary); + background-color: rgba(from var(--warning) r g b / 0.1); + color: var(--text-primary); } .alert-warning::before, .alert-warning::after { - background-color: var(--warning-color); + background-color: var(--warning); } .alert-warning .alert-icon { - color: var(--warning-color); + color: var(--warning); } /* Alert types - Info */ .alert-info { - background-color: rgba(56, 189, 248, 0.1); - color: var(--text-primary); + background-color: rgba(from var(--info) r g b / 0.1); + color: var(--text-primary); } .alert-info::before, .alert-info::after { - background-color: var(--info-color); + background-color: var(--info); } .alert-info .alert-icon { - color: var(--info-color); -} - -/* Dark mode adjustments */ -[data-theme='dark'] .alert-success { - background-color: rgba(34, 197, 94, 0.15); -} - -[data-theme='dark'] .alert-danger { - background-color: rgba(239, 68, 68, 0.15); + color: var(--info); } -[data-theme='dark'] .alert-warning { - background-color: rgba(251, 191, 36, 0.15); -} - -[data-theme='dark'] .alert-info { - background-color: rgba(56, 189, 248, 0.15); -} +/* Dark mode is handled by variables.css */ /* Animations */ @keyframes slideInRight { - from { - transform: translateX(100%); - opacity: 0; - } - - to { - transform: translateX(0); - opacity: 1; - } + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } } @keyframes progress { - from { - width: 100%; - } - - to { - width: 0; - } + from { + width: 100%; + } + to { + width: 0; + } } /* Responsive adjustments */ @media (max-width: 576px) { - .alert-container { - max-width: 100%; - width: 94%; - top: 60px; - } + .alert-container { + max-width: 100%; + width: 94%; + top: 60px; + } - .custom-alert { - padding: 0.85rem 1rem; - font-size: 0.9rem; - } + .custom-alert { + padding: var(--space-3) var(--space-4); + font-size: var(--text-sm); + } - .alert-icon { - font-size: 1.25rem; - } + .alert-icon { + font-size: 1.25rem; + } - .alert-content { - margin: 0 0.75rem; - } + .alert-content { + margin: 0 var(--space-3); + } } -/* Landscape mode adjustments */ @media (max-height: 500px) and (orientation: landscape) { - .alert-container { - max-width: 300px; - top: 50px; - } - - .custom-alert { - padding: 0.75rem 1rem; - } + .alert-container { + max-width: 300px; + top: 50px; + } + + .custom-alert { + padding: var(--space-3) var(--space-4); + } } diff --git a/auctions/static/css/components/card.css b/auctions/static/css/components/card.css index 53bfac2..ee5a99f 100644 --- a/auctions/static/css/components/card.css +++ b/auctions/static/css/components/card.css @@ -1,141 +1,133 @@ -/* Enhanced Card Styles */ +/* ================================= + AUCTION CARD COMPONENT STYLES + ================================= */ + .auction-card { - transition: all 0.3s ease; - background-color: var(--card-bg); - box-shadow: var(--shadow-sm); - border-radius: 0.75rem; - overflow: hidden; + transition: var(--transition-all); + background-color: var(--bg-elevated); + box-shadow: var(--shadow-sm); + border-radius: var(--radius-xl); + overflow: hidden; + border: 1px solid var(--border-light); } .auction-card:hover { - transform: translateY(-5px); - box-shadow: var(--hover-shadow); + transform: translateY(-5px); + box-shadow: var(--shadow-lg); } .auction-card .image-container { - height: 240px; - width: 100%; - background-color: var(--bg-secondary); - position: relative; - border-radius: 0.75rem 0 0 0.75rem; + height: 240px; + width: 100%; + background-color: var(--bg-secondary); + position: relative; + border-radius: var(--radius-xl) 0 0 var(--radius-xl); } -/* Image styling for proper fit without distortion */ +/* Image styling */ .auction-card .auction-image { - width: 100%; - height: 100%; - object-fit: scale-down; - object-position: center; + width: 100%; + height: 100%; + object-fit: cover; /* Changed to cover for better aesthetics */ + object-position: center; } -/* Placeholder styling */ +/* Placeholder for no image */ .auction-card .no-image-placeholder { - height: 100%; - display: flex; - align-items: center; - justify-content: center; - background-color: var(--bg-secondary); - color: var(--text-muted); + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background-color: var(--bg-secondary); + color: var(--text-muted); } .auction-card .card-title { - color: var(--text-primary); - font-weight: 600; + color: var(--text-primary); + font-weight: var(--font-semibold); } .auction-card .description-text { - color: var(--text-secondary); - display: -webkit-box; - -webkit-line-clamp: 3; - -webkit-box-orient: vertical; - overflow: hidden; - line-height: 1.5; + color: var(--text-secondary); + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; + line-height: var(--leading-normal); } .auction-card .bids-count { - color: var(--bid-color); - font-size: 0.9rem; - font-weight: 600; + color: var(--bid-current); + font-size: var(--text-sm); + font-weight: var(--font-semibold); } .auction-card .category-tag .badge { - background-color: var(--bg-accent); - color: var(--text-light); + background-color: var(--primary-100); + color: var(--primary-700); } .card-badges .badge-ending-soon { - animation: pulse 2s infinite; + animation: pulse 2s infinite; } /* Button in card footer */ .auction-card .btn-primary { - padding: 0.5rem 1rem; - border-radius: 20px; - font-weight: 500; + padding: var(--space-2) var(--space-4); + border-radius: var(--radius-full); + font-weight: var(--font-medium); } -/* Dark mode adjustments */ -[data-theme='dark'] .auction-card { - background-color: var(--card-bg); +.price-tag { + font-weight: var(--font-bold); + color: var(--success); + display: flex; + align-items: center; + gap: var(--space-2); } -[data-theme='dark'] .auction-card .no-image-placeholder { - background-color: rgba(30, 41, 59, 0.6); -} +/* Dark mode support is handled by variables.css */ -[data-theme='dark'] .auction-card .btn-close { - background-color: rgba(30, 41, 59, 0.8); - color: var(--text-light); -} +/* Responsive adjustments */ +@media (max-width: 767px) { + .auction-card .image-container { + height: 200px; + border-radius: var(--radius-xl) var(--radius-xl) 0 0; + } -.price-tag { - font-weight: 700; - color: var(--price-color); - display: flex; - align-items: center; - gap: 0.5rem; -} + .auction-card .card-body { + padding: var(--space-5); + } -/* Mobile Responsive Adjustments */ -@media (max-width: 767px) { - .auction-card .image-container { - height: 200px; /* Slightly smaller on tablets */ - border-radius: 0.75rem 0.75rem 0 0; - } - - .auction-card .card-body { - padding: 1.25rem; - } - - .auction-card .description-text { - -webkit-line-clamp: 2; - font-size: 0.9rem; - } - - .auction-card .price-tag { - font-size: 1.1rem; - } + .auction-card .description-text { + -webkit-line-clamp: 2; + font-size: var(--text-sm); + } + + .auction-card .price-tag { + font-size: var(--text-lg); + } } @media (max-width: 576px) { - .auction-card { - border-radius: 0.5rem; - } - - .auction-card .card-body { - padding: 1rem; - } - - .auction-card .image-container { - height: 180px; /* Even smaller on phones */ - border-radius: 0.5rem 0.5rem 0 0; - } - - .card-badges { - font-size: 0.75rem; - } - - .auction-card .btn-primary { - padding: 0.35rem 0.7rem; - } + .auction-card { + border-radius: var(--radius-lg); + } + + .auction-card .card-body { + padding: var(--space-4); + } + + .auction-card .image-container { + height: 180px; + border-radius: var(--radius-lg) var(--radius-lg) 0 0; + } + + .card-badges { + font-size: var(--text-xs); + } + + .auction-card .btn-primary { + padding: var(--space-1) var(--space-3); + } } diff --git a/auctions/static/css/components/footer.css b/auctions/static/css/components/footer.css index 044244d..37d0a97 100644 --- a/auctions/static/css/components/footer.css +++ b/auctions/static/css/components/footer.css @@ -1,307 +1,262 @@ -/* Enhanced Footer Styles */ +/* ================================= + FOOTER COMPONENT STYLES + ================================= */ + .footer { - background-color: var(--bg-secondary); - color: var(--text-primary); - padding: 3rem 0 2rem; - margin-top: auto; - border-top: 1px solid var(--border-light); - position: relative; + background-color: var(--bg-secondary); + color: var(--text-primary); + padding: var(--space-12) 0 var(--space-8); + margin-top: auto; + border-top: 1px solid var(--border-light); + position: relative; } -/* Footer Brand Section */ +/* Footer brand section */ .footer-brand h5 { - color: var(--primary-color); - font-size: 1.5rem; - font-weight: 700; - margin-bottom: 1rem; - display: flex; - align-items: center; + color: var(--primary-500); + font-size: var(--text-xl); + font-weight: var(--font-bold); + margin-bottom: var(--space-4); + display: flex; + align-items: center; } .footer-brand h5 i { - color: var(--secondary-color); + color: var(--secondary-500); } .footer-description { - color: var(--text-secondary); - font-size: 0.95rem; - line-height: 1.6; - margin-bottom: 1.5rem; - max-width: 90%; + color: var(--text-secondary); + font-size: var(--text-base); + line-height: var(--leading-relaxed); + margin-bottom: var(--space-6); + max-width: 90%; } -/* Social Media Links */ +/* Social media links */ .social-links { - display: flex; - gap: 1rem; - margin-top: 1.5rem; + display: flex; + gap: var(--space-4); + margin-top: var(--space-6); } .social-links a { - display: flex; - align-items: center; - justify-content: center; - width: 40px; - height: 40px; - border-radius: 50%; - background: var(--bg-hover); - color: var(--primary-color); - font-size: 1.2rem; - transition: all 0.3s ease; + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + border-radius: var(--radius-full); + background: var(--primary-100); + color: var(--primary-500); + font-size: 1.2rem; + transition: var(--transition-all); } .social-links a:hover { - transform: translateY(-3px); - background: var(--primary-color); - color: var(--text-light); - box-shadow: 0 5px 15px rgba(15, 91, 171, 0.3); + transform: translateY(-3px); + background: var(--primary-500); + color: var(--white); + box-shadow: 0 5px 15px rgba(from var(--primary-500) r g b / 0.3); } -/* Section Headings */ +/* Section headings */ .footer-heading { - color: var(--primary-color); - font-size: 1.2rem; - font-weight: 600; - margin-bottom: 1.5rem; - position: relative; - padding-bottom: 0.75rem; + color: var(--primary-500); + font-size: var(--text-lg); + font-weight: var(--font-semibold); + margin-bottom: var(--space-6); + position: relative; + padding-bottom: var(--space-3); } .footer-heading::after { - content: ''; - position: absolute; - bottom: 0; - left: 0; - width: 50px; - height: 3px; - background: var(--secondary-color); - border-radius: 2px; + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 50px; + height: 3px; + background: var(--secondary-500); + border-radius: var(--radius-sm); } -/* Quick Links */ +/* Quick links */ .footer-links { - list-style: none; - padding: 0; - margin: 0; + list-style: none; + padding: 0; + margin: 0; } .footer-links li { - margin-bottom: 1rem; + margin-bottom: var(--space-4); } .footer-links a { - color: var(--text-secondary); - text-decoration: none; - font-size: 0.95rem; - display: flex; - align-items: center; - transition: all 0.2s ease; + color: var(--text-secondary); + text-decoration: none; + font-size: var(--text-base); + display: flex; + align-items: center; + transition: var(--transition-all); } .footer-links a i { - color: var(--primary-color); - opacity: 0.8; - transition: transform 0.2s ease; + color: var(--primary-500); + opacity: 0.8; + transition: transform 0.2s ease; } .footer-links a:hover { - color: var(--primary-color); - transform: translateX(5px); + color: var(--primary-500); + transform: translateX(5px); } .footer-links a:hover i { - opacity: 1; - transform: scale(1.1); + opacity: 1; + transform: scale(1.1); } -/* Contact Information */ +/* Contact information */ .footer-contact { - list-style: none; - padding: 0; - margin: 0; + list-style: none; + padding: 0; + margin: 0; } .footer-contact li { - margin-bottom: 1rem; - display: flex; - align-items: flex-start; + margin-bottom: var(--space-4); + display: flex; + align-items: flex-start; } .footer-contact li i { - color: var(--secondary-color); - margin-right: 1rem; - margin-top: 0.25rem; - font-size: 1.1rem; + color: var(--secondary-500); + margin-right: var(--space-4); + margin-top: var(--space-1); + font-size: 1.1rem; } .footer-contact li span, .footer-contact li a { - color: var(--text-secondary); - font-size: 0.95rem; - text-decoration: none; - transition: all 0.2s ease; + color: var(--text-secondary); + font-size: var(--text-base); + text-decoration: none; + transition: var(--transition-all); } .footer-contact li a:hover { - color: var(--primary-color); + color: var(--primary-500); } -/* Footer Divider */ +/* Footer divider */ .footer-divider { - height: 1px; - background: linear-gradient( - to right, - transparent, - var(--border-color), - transparent - ); - margin: 2rem 0 1.5rem; - opacity: 0.5; + height: 1px; + background: linear-gradient(to right, transparent, var(--border-medium), transparent); + margin: var(--space-8) 0 var(--space-6); + opacity: 0.5; } -/* Footer Bottom */ +/* Footer bottom section */ .footer-bottom { - display: flex; - justify-content: space-between; - align-items: center; - flex-wrap: wrap; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; } .copyright { - color: var(--text-muted); - font-size: 0.9rem; + color: var(--text-muted); + font-size: var(--text-sm); } .footer-legal { - display: flex; - gap: 1.5rem; + display: flex; + gap: var(--space-6); } .footer-legal a { - color: var(--text-muted); - font-size: 0.9rem; - text-decoration: none; - transition: color 0.2s ease; + color: var(--text-muted); + font-size: var(--text-sm); + text-decoration: none; + transition: color 0.2s ease; } .footer-legal a:hover { - color: var(--primary-color); -} - -/* Dark Mode Adjustments */ -[data-theme='dark'] .footer { - background-color: rgba(22, 27, 34, 0.95); - border-top: 1px solid var(--border-color); + color: var(--primary-500); } -[data-theme='dark'] .footer-brand h5 { - color: var(--primary-light); -} - -[data-theme='dark'] .social-links a { - background: rgba(255, 255, 255, 0.05); - color: var(--primary-light); -} +/* Dark mode support is handled by variables.css */ -[data-theme='dark'] .social-links a:hover { - background: var(--primary-color); - color: var(--text-light); -} - -[data-theme='dark'] .footer-heading { - color: var(--primary-light); -} - -[data-theme='dark'] .footer-links a { - color: var(--text-secondary); -} - -[data-theme='dark'] .footer-links a:hover { - color: var(--primary-light); -} - -[data-theme='dark'] .footer-contact li i { - color: var(--secondary-light); +/* Responsive styles */ +@media (max-width: 992px) { + .footer { + padding: var(--space-10) 0 var(--space-6); + } } -[data-theme='dark'] .footer-contact li span, -[data-theme='dark'] .footer-contact li a { - color: var(--text-secondary); -} +@media (max-width: 768px) { + .footer-brand h5 { + font-size: var(--text-lg); + } -[data-theme='dark'] .footer-contact li a:hover { - color: var(--primary-light); -} + .footer-heading { + font-size: var(--text-base); + margin-bottom: var(--space-5); + } -/* Responsive Styles */ -@media (max-width: 992px) { - .footer { - padding: 2.5rem 0 1.5rem; - } -} + .footer-bottom { + flex-direction: column; + text-align: center; + gap: var(--space-4); + } -@media (max-width: 768px) { - .footer-brand h5 { - font-size: 1.3rem; - } - - .footer-heading { - font-size: 1.1rem; - margin-bottom: 1.2rem; - } - - .footer-bottom { - flex-direction: column; - text-align: center; - gap: 1rem; - } - - .footer-legal { - justify-content: center; - } + .footer-legal { + justify-content: center; + } } @media (max-width: 576px) { - .footer { - padding: 2rem 0 1.5rem; - } - - .footer-brand h5 { - font-size: 1.2rem; - justify-content: center; - } - - .footer-description { - text-align: center; - margin-left: auto; - margin-right: auto; - } - - .social-links { - justify-content: center; - } - - .footer-heading { - text-align: center; - font-size: 1rem; - } - - .footer-heading::after { - left: 50%; - transform: translateX(-50%); - } - - .footer-links a, - .footer-contact li { - font-size: 0.9rem; - } - - .footer-links { - margin-bottom: 1.5rem; - } - - .footer-legal { - flex-direction: column; - gap: 0.5rem; - } + .footer { + padding: var(--space-8) 0 var(--space-6); + } + + .footer-brand h5 { + font-size: var(--text-lg); + justify-content: center; + } + + .footer-description { + text-align: center; + margin-left: auto; + margin-right: auto; + } + + .social-links { + justify-content: center; + } + + .footer-heading { + text-align: center; + font-size: var(--text-base); + } + + .footer-heading::after { + left: 50%; + transform: translateX(-50%); + } + + .footer-links a, + .footer-contact li { + font-size: var(--text-sm); + } + + .footer-links { + margin-bottom: var(--space-6); + } + + .footer-legal { + flex-direction: column; + gap: var(--space-2); + } } diff --git a/auctions/static/css/components/navbar.css b/auctions/static/css/components/navbar.css deleted file mode 100644 index 98d04a9..0000000 --- a/auctions/static/css/components/navbar.css +++ /dev/null @@ -1,406 +0,0 @@ -/* Navbar Base Styles */ -.navbar { - box-shadow: var(--shadow-lg); - padding: 0.75rem 1.5rem; - position: sticky; - top: 0; - z-index: 1000; - transition: all 0.3s ease; - background: var(--bg-main); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - border-bottom: 1px solid var(--border-light); -} - -/* Brand/Logo */ -.brand-title { - font-size: 1.75rem; - color: var(--secondary-color); - font-weight: 700; - text-decoration: none; - transition: all 0.3s ease; - display: flex; - align-items: center; - gap: 0.5rem; - letter-spacing: -0.5px; - position: relative; -} - -.brand-title:hover { - color: var(--secondary-dark); - transform: translateY(-2px); -} - -.brand-title:focus { - outline: 2px solid var(--primary-color); - outline-offset: 3px; - border-radius: 4px; -} - -.brand-title i { - font-size: 1.5rem; - color: var(--bid-color); -} - -/* Navigation Links */ -.navbar-nav { - display: flex; - align-items: center; - gap: 0.75rem; -} - -.nav-item { - position: relative; -} - -.nav-link { - color: var(--text-secondary); - font-weight: 600; - padding: 0.6rem 1.2rem; - border-radius: 8px; - transition: all 0.3s ease; - display: flex; - align-items: center; - gap: 0.5rem; - font-size: 0.95rem; - position: relative; - overflow: hidden; -} - -.nav-link:hover { - background: var(--bg-hover); - color: var(--primary-color); - transform: translateY(-2px); -} - -.nav-link:focus { - outline: 2px solid var(--primary-color); - outline-offset: -2px; -} - -.nav-link.active { - background: var(--gradient-primary); - color: var(--text-light); - box-shadow: var(--shadow-sm); -} - -.nav-link.active:hover { - transform: translateY(-2px); - box-shadow: var(--shadow-md); -} - -/* Icon in Nav Link */ -.nav-link i { - font-size: 1rem; - transition: transform 0.3s ease; -} - -.nav-link:hover i { - transform: scale(1.2); -} - -/* Badge for notifications */ -.nav-link .badge { - position: absolute; - top: 0.3rem; - right: 0.3rem; - font-size: 0.7rem; - padding: 0.25em 0.5em; - border-radius: 50%; - background: var(--badge-ending); - color: var(--text-light); - font-weight: 700; - transform: translate(25%, -25%); - transition: all 0.3s ease; -} - -/* User Info Section */ -.user-info { - background: var(--bg-user); - padding: 0.6rem 1.2rem; - border-radius: 20px; - font-size: 0.9rem; - display: flex; - align-items: center; - gap: 0.5rem; - transition: all 0.3s ease; - color: var(--text-primary); - font-weight: 600; - box-shadow: var(--shadow-sm); -} - -.user-info:hover { - background: var(--bg-hover); - transform: translateY(-2px); - box-shadow: var(--shadow-md); -} - -.user-info i { - color: var(--primary-color); -} - -/* Theme Toggle Button */ -.theme-toggle { - background: transparent; - border: none; - padding: 0.6rem; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: all 0.3s ease; - color: var(--toggle-icon); - font-size: 1.75rem; - margin-left: 1rem; -} - -.theme-toggle:hover { - background: var(--bg-hover); - transform: rotate(20deg); -} - -.theme-toggle:focus { - outline: 2px solid var(--primary-color); - outline-offset: 2px; -} - -/* Navbar Toggler */ -.navbar-toggler { - border: none; - padding: 0.6rem; - border-radius: 8px; - transition: all 0.3s ease; - background: var(--bg-hover); -} - -.navbar-toggler:hover { - background: var(--bg-secondary); -} - -.navbar-toggler:focus { - box-shadow: none; - outline: 2px solid var(--primary-color); - outline-offset: 2px; -} - -.navbar-toggler-icon { - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(15, 91, 171, 1)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); -} - -[data-theme='dark'] .navbar-toggler-icon { - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(227, 240, 255, 1)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); -} - -/* Responsive Navbar Styles */ -@media (max-width: 991px) { - .navbar { - padding: 0.5rem 1rem; - } - - .navbar-collapse { - position: fixed; - top: 70px; - left: 0; - right: 0; - background: var(--bg-main); - padding: 1rem; - box-shadow: var(--shadow-lg); - border-radius: 0 0 15px 15px; - max-height: calc(100vh - 70px); - overflow-y: auto; - transition: all 0.3s ease; - z-index: 1000; - border: 1px solid var(--border-light); - } - - .navbar-nav { - gap: 0.75rem; - margin: 1rem 0; - } - - .nav-item { - width: 100%; - } - - .nav-link { - padding: 0.85rem 1.2rem; - border-radius: 8px; - display: flex; - justify-content: space-between; - align-items: center; - font-size: 1rem; - color: var(--text-primary); - } - - .nav-link i { - font-size: 1.2rem; - } - - .nav-link.active { - background: var(--gradient-primary); - color: var(--text-light); - box-shadow: var(--shadow-sm); - } - - .user-info { - margin: 1.25rem 0; - justify-content: center; - width: 100%; - padding: 1rem; - border-radius: 8px; - font-size: 1rem; - background: var(--bg-secondary); - box-shadow: var(--shadow-sm); - } - - .theme-toggle { - position: relative; - margin-right: 1rem; - font-size: 1.4rem; - } - - .brand-title { - font-size: 1.4rem; - } - - .brand-title i { - font-size: 1.2rem; - } -} - -/* Mobile Specific Styles */ -@media (max-width: 576px) { - .navbar { - padding: 0.5rem; - } - - .navbar-collapse { - top: 60px; - max-height: calc(100vh - 60px); - padding: 0.75rem; - } - - .nav-link { - padding: 0.75rem 1rem; - font-size: 0.95rem; - } - - .nav-link i { - font-size: 1.1rem; - } - - .nav-link .badge { - font-size: 0.65rem; - } - - .user-info { - padding: 0.75rem; - font-size: 0.9rem; - margin: 1rem 0; - } - - .brand-title { - font-size: 1.2rem; - } - - .brand-title i { - font-size: 1.1rem; - } - - .theme-toggle { - font-size: 1.2rem; - } -} - -/* Dark Mode Specific Adjustments */ -[data-theme='dark'] .navbar { - border-bottom-color: var(--border-color); -} - -[data-theme='dark'] .brand-title { - color: var(--secondary-light); -} - -[data-theme='dark'] .brand-title:hover { - color: var(--secondary-color); -} - -[data-theme='dark'] .nav-link { - color: var(--text-secondary); -} - -[data-theme='dark'] .nav-link:hover { - background: var(--bg-hover); - color: var(--primary-light); -} - -[data-theme='dark'] .nav-link.active { - background: var(--gradient-primary); - color: var(--text-light); -} - -[data-theme='dark'] .navbar-toggler { - background: rgba(255, 255, 255, 0.05); -} - -[data-theme='dark'] .navbar-toggler:hover { - background: rgba(255, 255, 255, 0.1); -} - -[data-theme='dark'] .theme-toggle { - color: var(--toggle-icon); -} - -[data-theme='dark'] .navbar-collapse { - background: var(--bg-main); - border-color: var(--border-color); -} - -/* Animation */ -@keyframes slideDown { - from { - transform: translateY(-100%); - opacity: 0; - } - - to { - transform: translateY(0); - opacity: 1; - } -} - -@keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } -} - -.navbar { - animation: slideDown 0.3s ease-out; -} - -.navbar-collapse.show { - animation: fadeIn 0.2s ease-out; -} - -/* Accessibility Improvements */ -.nav-link:focus-visible, -.brand-title:focus-visible, -.theme-toggle:focus-visible, -.user-info:focus-visible { - outline: 2px solid var(--primary-color); - outline-offset: 2px; - box-shadow: 0 0 0 4px rgba(15, 91, 171, 0.2); -} - -[data-theme='dark'] .nav-link:focus-visible, -[data-theme='dark'] .brand-title:focus-visible, -[data-theme='dark'] .theme-toggle:focus-visible, -[data-theme='dark'] .user-info:focus-visible { - outline-color: var(--primary-light); - box-shadow: 0 0 0 4px rgba(227, 240, 255, 0.2); -} diff --git a/auctions/static/css/components/navigation.css b/auctions/static/css/components/navigation.css new file mode 100644 index 0000000..956a965 --- /dev/null +++ b/auctions/static/css/components/navigation.css @@ -0,0 +1,414 @@ +/* ======================================== + NAVIGATION COMPONENT STYLES + ======================================== */ + +/* Navigation Container */ +.navbar { + background: var(--nav-gradient); + box-shadow: var(--shadow-lg); + padding: var(--space-4) 0; + position: sticky; + top: 0; + z-index: var(--z-sticky); + backdrop-filter: blur(10px); + border-bottom: 1px solid var(--border-light); +} + +.navbar .container { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: var(--space-4); +} + +/* Theme Toggle Button */ +.theme-toggle { + background: rgba(255, 255, 255, 0.15); + border: 2px solid rgba(255, 255, 255, 0.2); + color: var(--text-inverse); + border-radius: var(--radius-full); + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: var(--transition-all); + margin-right: var(--space-4); + position: relative; + overflow: hidden; +} + +.theme-toggle:hover { + background: rgba(255, 255, 255, 0.25); + border-color: rgba(255, 255, 255, 0.3); + transform: scale(1.05); + box-shadow: var(--shadow-md); +} + +.theme-toggle:active { + transform: scale(0.95); +} + +.theme-toggle i { + font-size: var(--text-lg); + transition: var(--transition-all); + z-index: 1; + position: relative; +} + +/* Brand/Logo */ +.brand-title { + color: var(--text-inverse); + text-decoration: none; + font-size: var(--text-2xl); + font-weight: var(--font-bold); + font-family: var(--font-display); + display: flex; + align-items: center; + transition: var(--transition-all); + position: relative; + padding: var(--space-2) var(--space-3); + border-radius: var(--radius-lg); +} + +.brand-title::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 255, 255, 0.1); + border-radius: var(--radius-lg); + opacity: 0; + transition: var(--transition-all); +} + +.brand-title:hover::before { + opacity: 1; +} + +.brand-title:hover { + color: var(--text-inverse); + text-decoration: none; + transform: translateY(-2px); +} + +.brand-title i { + font-size: var(--text-xl); + margin-right: var(--space-2); + color: var(--accent-gold-light); + transition: var(--transition-all); +} + +.brand-title:hover i { + transform: rotate(15deg) scale(1.1); + color: var(--accent-gold); +} + +/* Mobile Menu Toggle */ +.navbar-toggler { + border: 2px solid rgba(255, 255, 255, 0.3); + background: rgba(255, 255, 255, 0.1); + color: var(--text-inverse); + padding: var(--space-2); + border-radius: var(--radius-md); + transition: var(--transition-all); + position: relative; + overflow: hidden; +} + +.navbar-toggler::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 255, 255, 0.1); + opacity: 0; + transition: var(--transition-all); +} + +.navbar-toggler:hover::before { + opacity: 1; +} + +.navbar-toggler:hover { + background: rgba(255, 255, 255, 0.2); + border-color: rgba(255, 255, 255, 0.4); + transform: scale(1.05); +} + +.navbar-toggler:focus { + outline: none; + box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.3); +} + +.navbar-toggler-icon { + display: block; + width: 20px; + height: 2px; + background: var(--text-inverse); + position: relative; + transition: var(--transition-all); +} + +.navbar-toggler-icon::before, +.navbar-toggler-icon::after { + content: ''; + position: absolute; + width: 100%; + height: 2px; + background: var(--text-inverse); + transition: var(--transition-all); +} + +.navbar-toggler-icon::before { + top: -6px; +} + +.navbar-toggler-icon::after { + top: 6px; +} + +/* Navigation Links Container */ +.navbar-nav { + display: flex; + align-items: center; + gap: var(--space-1); + margin: 0; + padding: 0; + list-style: none; +} + +/* Individual Navigation Links */ +.nav-link { + color: rgba(255, 255, 255, 0.9); + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-lg); + transition: var(--transition-all); + display: flex; + align-items: center; + text-decoration: none; + position: relative; + font-weight: var(--font-medium); + font-size: var(--text-sm); + white-space: nowrap; + overflow: hidden; +} + +.nav-link:hover { + color: var(--text-inverse); + background: rgba(255, 255, 255, 0.15); + transform: translateY(-2px); + box-shadow: var(--shadow-md); +} + +.nav-link.active { + color: var(--text-inverse) !important; + background: rgba(255, 255, 255, 0.2); + font-weight: var(--font-semibold); + box-shadow: var(--shadow-sm); +} + +.nav-link.active::after { + content: ''; + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: 20px; + height: 2px; + background: var(--accent-gold); + border-radius: var(--radius-full); +} + +/* Navigation Icons */ +.nav-link i { + margin-right: var(--space-2); + width: 18px; + text-align: center; + font-size: var(--text-sm); + transition: var(--transition-all); +} + +.nav-link:hover i { + transform: scale(1.1); +} + +.nav-link.active i { + color: var(--accent-gold-light); +} + +/* Badge in Navigation */ +.nav-link .badge { + margin-left: var(--space-2); + font-size: var(--text-xs); + font-weight: var(--font-semibold); + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-full); + background: var(--accent-red) !important; + color: var(--text-inverse) !important; + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0%, + 100% { + transform: scale(1); + } + 50% { + transform: scale(1.05); + } +} + +/* User Info Section */ +.user-info { + color: var(--text-inverse); + display: flex; + align-items: center; + padding: var(--space-2) var(--space-4); + background: rgba(255, 255, 255, 0.1); + border-radius: var(--radius-lg); + margin-left: var(--space-4); + transition: var(--transition-all); + border: 1px solid rgba(255, 255, 255, 0.1); + position: relative; + overflow: hidden; +} + +.user-info::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.1), + transparent + ); + transition: left 0.5s; +} + +.user-info:hover::before { + left: 100%; +} + +.user-info:hover { + background: rgba(255, 255, 255, 0.15); + transform: translateY(-1px); + box-shadow: var(--shadow-md); +} + +.user-info i { + margin-right: var(--space-2); + color: var(--accent-gold-light); + font-size: var(--text-sm); +} + +.user-info strong { + font-weight: var(--font-semibold); + font-size: var(--text-sm); +} + +/* Responsive Design */ +@media (max-width: 768px) { + .navbar .container { + flex-direction: column; + gap: var(--space-3); + } + + .navbar-nav { + flex-direction: column; + width: 100%; + gap: var(--space-1); + } + + .nav-link { + width: 100%; + justify-content: center; + padding: var(--space-3) var(--space-4) !important; + text-align: center; + } + + .user-info { + margin-left: 0; + margin-top: var(--space-2); + justify-content: center; + width: 100%; + } + + .theme-toggle { + margin-right: 0; + order: -1; + } + + .brand-title { + order: -2; + } +} + +@media (max-width: 576px) { + .brand-title { + font-size: var(--text-xl); + } + + .theme-toggle { + width: 44px; + height: 44px; + } + + .nav-link { + font-size: var(--text-xs); + padding: var(--space-2) var(--space-3) !important; + } + + .user-info { + padding: var(--space-2) var(--space-3); + } +} + +/* High Contrast Mode Support */ +@media (prefers-contrast: high) { + .nav-link { + border: 1px solid rgba(255, 255, 255, 0.3); + } + + .nav-link:hover, + .nav-link.active { + border-color: rgba(255, 255, 255, 0.6); + } + + .theme-toggle { + border-width: 3px; + } +} + +/* Reduced Motion Support */ +@media (prefers-reduced-motion: reduce) { + .nav-link::before, + .theme-toggle::before, + .user-info::before { + display: none; + } + + .nav-link, + .theme-toggle, + .user-info, + .brand-title { + transition: none; + } + + .nav-link:hover, + .theme-toggle:hover, + .user-info:hover, + .brand-title:hover { + transform: none; + } +} diff --git a/auctions/static/css/components/pagination.css b/auctions/static/css/components/pagination.css index f849c25..ee57c79 100644 --- a/auctions/static/css/components/pagination.css +++ b/auctions/static/css/components/pagination.css @@ -1,156 +1,140 @@ -/* Pagination Component */ +/* ================================= + PAGINATION COMPONENT STYLES + ================================= */ + .pagination-container { - margin: 2rem 0; - padding: 1rem; - overflow-x: auto; /* For mobile responsiveness */ + margin: var(--space-8) 0; + padding: var(--space-4); + overflow-x: auto; /* For mobile responsiveness */ } .pagination { - gap: 0.5rem; - flex-wrap: nowrap; /* Prevent wrapping on small screens */ + gap: var(--space-2); + flex-wrap: nowrap; /* Prevent wrapping on small screens */ } .page-item:not(.disabled) .page-link { - color: var(--primary-color); - background-color: var(--bg-main); - border-color: var(--border-light); - transition: all 0.3s ease; + color: var(--primary-500); + background-color: var(--bg-primary); + border-color: var(--border-light); + transition: var(--transition-all); } .page-item:not(.disabled) .page-link:hover { - background-color: var(--bg-hover); - color: var(--primary-color); - transform: translateY(-2px); - box-shadow: var(--shadow-sm); + background-color: var(--primary-50); + color: var(--primary-600); + transform: translateY(-2px); + box-shadow: var(--shadow-sm); } .page-item:not(.disabled) .page-link:focus { - box-shadow: 0 0 0 0.25rem rgba(15, 91, 171, 0.25); - z-index: 3; - outline: none; + box-shadow: 0 0 0 0.25rem rgba(from var(--primary-500) r g b / 0.25); + z-index: 3; + outline: none; } .page-item:not(.disabled) .page-link:active { - transform: translateY(0); - background-color: var(--bg-hover); + transform: translateY(0); + background-color: var(--primary-100); } .page-link { - border-radius: 8px; - border: 1px solid var(--border-light); - padding: 0.5rem 1rem; - font-weight: 600; - font-size: 0.95rem; - display: flex; - align-items: center; - justify-content: center; - min-width: 2.5rem; - height: 2.5rem; + border-radius: var(--radius-lg); + border: 1px solid var(--border-light); + padding: var(--space-2) var(--space-4); + font-weight: var(--font-semibold); + font-size: var(--text-base); + display: flex; + align-items: center; + justify-content: center; + min-width: 2.5rem; + height: 2.5rem; } /* Current page */ .page-item.active .page-link { - background: var(--gradient-primary); - color: var(--text-light) !important; - border-color: transparent; - box-shadow: var(--shadow-sm); - position: relative; + background: var(--nav-gradient); + color: var(--white) !important; + border-color: transparent; + box-shadow: var(--shadow-sm); + position: relative; } .current-page { - min-width: 180px; - justify-content: center; - display: flex; - align-items: center; + min-width: 180px; + justify-content: center; + display: flex; + align-items: center; } /* Disabled state */ .page-item.disabled .page-link { - color: var(--text-muted); - background-color: var(--bg-secondary); - border-color: var(--border-light); - pointer-events: none; - opacity: 0.6; + color: var(--text-muted); + background-color: var(--bg-secondary); + border-color: var(--border-light); + pointer-events: none; + opacity: 0.6; } /* Icon style */ .page-link i { - font-size: 0.8rem; + font-size: var(--text-sm); } -/* Dark mode styles */ -[data-theme='dark'] .page-item:not(.disabled):not(.active) .page-link { - background-color: var(--card-bg); - border-color: var(--border-color); - color: var(--primary-light); -} +/* Dark mode support is handled by variables.css */ -[data-theme='dark'] .page-item:not(.disabled):not(.active) .page-link:hover { - background-color: var(--bg-hover); -} +/* Responsive styles */ +@media (max-width: 767px) { + .pagination-container { + margin: var(--space-6) 0; + padding: var(--space-3); + } -[data-theme='dark'] .page-item.disabled .page-link { - background-color: rgba(30, 41, 59, 0.5); - border-color: var(--border-color); - color: var(--text-muted); -} + .pagination { + gap: var(--space-1); + } -[data-theme='dark'] .page-item.active .page-link { - background: var(--gradient-primary); -} + .page-link { + padding: var(--space-2); + min-width: 2.25rem; + height: 2.25rem; + font-size: var(--text-sm); + } -/* Responsive styles */ -@media (max-width: 767px) { - .pagination-container { - margin: 1.5rem 0; - padding: 0.75rem; - } - - .pagination { - gap: 0.25rem; - } - - .page-link { - padding: 0.4rem; - min-width: 2.25rem; - height: 2.25rem; - font-size: 0.9rem; - } - - .current-page { - min-width: auto; - font-size: 0.85rem; - padding: 0.4rem 0.75rem; - } + .current-page { + min-width: auto; + font-size: var(--text-sm); + padding: var(--space-2) var(--space-3); + } } @media (max-width: 575px) { - .pagination-container { - margin: 1rem 0; - padding: 0.5rem 0; - overflow-x: auto; - } - - .pagination { - display: flex; - flex-wrap: nowrap; - padding-bottom: 0.5rem; - } - - .page-link { - font-size: 0.8rem; - padding: 0.35rem; - min-width: 2rem; - height: 2rem; - } - - .current-page { - white-space: nowrap; - font-size: 0.8rem; - padding: 0.35rem 0.5rem; - } - - .page-link i { - font-size: 0.75rem; - } + .pagination-container { + margin: var(--space-4) 0; + padding: var(--space-2) 0; + overflow-x: auto; + } + + .pagination { + display: flex; + flex-wrap: nowrap; + padding-bottom: var(--space-2); + } + + .page-link { + font-size: var(--text-xs); + padding: var(--space-1); + min-width: 2rem; + height: 2rem; + } + + .current-page { + white-space: nowrap; + font-size: var(--text-xs); + padding: var(--space-1) var(--space-2); + } + + .page-link i { + font-size: 0.75rem; + } } diff --git a/auctions/static/css/layout.css b/auctions/static/css/layout.css new file mode 100644 index 0000000..ff0dafa --- /dev/null +++ b/auctions/static/css/layout.css @@ -0,0 +1,376 @@ +/* ======================================== + LAYOUT STYLES - Main Layout Components + ======================================== */ + +/* Import Variables and Typography */ +@import url('./variables.css'); + +/* Base Styles */ +* { + box-sizing: border-box; +} + +body { + font-family: var(--font-primary); + line-height: var(--leading-normal); + color: var(--text-primary); + background-color: var(--bg-secondary); + margin: 0; + padding: 0; + font-size: var(--text-base); + font-weight: var(--font-normal); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Skip Link for Accessibility */ +.skip-link { + position: absolute; + top: -40px; + left: var(--space-2); + background: var(--primary-600); + color: var(--text-inverse); + padding: var(--space-2); + text-decoration: none; + border-radius: var(--radius-md); + z-index: var(--z-tooltip); + transition: var(--transition-all); + font-weight: var(--font-medium); + font-size: var(--text-sm); +} + +.skip-link:focus { + top: var(--space-2); + box-shadow: var(--shadow-lg); +} + +/* Main Content */ +#main-content { + min-height: calc(100vh - 200px); + padding: var(--space-8) 0; + background-color: var(--bg-secondary); +} + +/* Back to Top Button */ +.back-to-top { + position: fixed; + bottom: var(--space-8); + right: var(--space-8); + background: var(--primary-600); + color: var(--text-inverse); + border: none; + border-radius: var(--radius-full); + width: 56px; + height: 56px; + display: none; + align-items: center; + justify-content: center; + cursor: pointer; + box-shadow: var(--shadow-xl); + transition: var(--transition-all); + z-index: var(--z-fixed); + font-size: var(--text-lg); +} + +.back-to-top:hover { + background: var(--primary-700); + transform: translateY(-3px); + box-shadow: var(--shadow-2xl); +} + +.back-to-top:focus { + outline: none; + box-shadow: 0 0 0 3px var(--primary-200); +} + +.back-to-top i { + font-size: var(--text-lg); + transition: var(--transition-all); +} + +.back-to-top:hover i { + transform: translateY(-2px); +} + +/* Responsive Design */ +@media (max-width: 768px) { + #main-content { + padding: var(--space-4) 0; + } + + .back-to-top { + bottom: var(--space-4); + right: var(--space-4); + width: 48px; + height: 48px; + } +} + +@media (max-width: 576px) { + .back-to-top { + width: 44px; + height: 44px; + bottom: var(--space-3); + right: var(--space-3); + } +} + +/* Loading States */ +.loading { + display: none; + text-align: center; + padding: var(--space-8); +} + +.spinner-border { + width: 3rem; + height: 3rem; + color: var(--primary-600); + border-width: 3px; +} + +/* Typography Utilities */ +.text-center { + text-align: center; +} +.text-left { + text-align: left; +} +.text-right { + text-align: right; +} + +.font-light { + font-weight: var(--font-light); +} +.font-normal { + font-weight: var(--font-normal); +} +.font-medium { + font-weight: var(--font-medium); +} +.font-semibold { + font-weight: var(--font-semibold); +} +.font-bold { + font-weight: var(--font-bold); +} + +.text-xs { + font-size: var(--text-xs); +} +.text-sm { + font-size: var(--text-sm); +} +.text-base { + font-size: var(--text-base); +} +.text-lg { + font-size: var(--text-lg); +} +.text-xl { + font-size: var(--text-xl); +} +.text-2xl { + font-size: var(--text-2xl); +} + +/* Display Utilities */ +.d-none { + display: none !important; +} +.d-block { + display: block !important; +} +.d-flex { + display: flex !important; +} +.d-inline { + display: inline !important; +} +.d-inline-block { + display: inline-block !important; +} + +/* Flexbox Utilities */ +.justify-content-center { + justify-content: center !important; +} +.justify-content-between { + justify-content: space-between !important; +} +.justify-content-around { + justify-content: space-around !important; +} +.justify-content-evenly { + justify-content: space-evenly !important; +} + +.align-items-center { + align-items: center !important; +} +.align-items-start { + align-items: flex-start !important; +} +.align-items-end { + align-items: flex-end !important; +} + +.flex-column { + flex-direction: column !important; +} +.flex-row { + flex-direction: row !important; +} +.flex-wrap { + flex-wrap: wrap !important; +} + +/* Spacing Utilities */ +.m-0 { + margin: 0 !important; +} +.m-1 { + margin: var(--space-1) !important; +} +.m-2 { + margin: var(--space-2) !important; +} +.m-3 { + margin: var(--space-3) !important; +} +.m-4 { + margin: var(--space-4) !important; +} +.m-5 { + margin: var(--space-5) !important; +} + +.mt-0 { + margin-top: 0 !important; +} +.mt-1 { + margin-top: var(--space-1) !important; +} +.mt-2 { + margin-top: var(--space-2) !important; +} +.mt-3 { + margin-top: var(--space-3) !important; +} +.mt-4 { + margin-top: var(--space-4) !important; +} +.mt-5 { + margin-top: var(--space-5) !important; +} + +.mb-0 { + margin-bottom: 0 !important; +} +.mb-1 { + margin-bottom: var(--space-1) !important; +} +.mb-2 { + margin-bottom: var(--space-2) !important; +} +.mb-3 { + margin-bottom: var(--space-3) !important; +} +.mb-4 { + margin-bottom: var(--space-4) !important; +} +.mb-5 { + margin-bottom: var(--space-5) !important; +} + +.ml-0 { + margin-left: 0 !important; +} +.ml-1 { + margin-left: var(--space-1) !important; +} +.ml-2 { + margin-left: var(--space-2) !important; +} +.ml-3 { + margin-left: var(--space-3) !important; +} +.ml-4 { + margin-left: var(--space-4) !important; +} +.ml-5 { + margin-left: var(--space-5) !important; +} + +.mr-0 { + margin-right: 0 !important; +} +.mr-1 { + margin-right: var(--space-1) !important; +} +.mr-2 { + margin-right: var(--space-2) !important; +} +.mr-3 { + margin-right: var(--space-3) !important; +} +.mr-4 { + margin-right: var(--space-4) !important; +} +.mr-5 { + margin-right: var(--space-5) !important; +} + +.p-0 { + padding: 0 !important; +} +.p-1 { + padding: var(--space-1) !important; +} +.p-2 { + padding: var(--space-2) !important; +} +.p-3 { + padding: var(--space-3) !important; +} +.p-4 { + padding: var(--space-4) !important; +} +.p-5 { + padding: var(--space-5) !important; +} + +/* Border Radius Utilities */ +.rounded { + border-radius: var(--radius-md) !important; +} +.rounded-sm { + border-radius: var(--radius-sm) !important; +} +.rounded-lg { + border-radius: var(--radius-lg) !important; +} +.rounded-xl { + border-radius: var(--radius-xl) !important; +} +.rounded-full { + border-radius: var(--radius-full) !important; +} + +/* Shadow Utilities */ +.shadow-sm { + box-shadow: var(--shadow-sm) !important; +} +.shadow { + box-shadow: var(--shadow-md) !important; +} +.shadow-lg { + box-shadow: var(--shadow-lg) !important; +} +.shadow-xl { + box-shadow: var(--shadow-xl) !important; +} +.shadow-none { + box-shadow: none !important; +} diff --git a/auctions/static/css/pages/auction.css b/auctions/static/css/pages/auction.css index 4f02e81..827640e 100644 --- a/auctions/static/css/pages/auction.css +++ b/auctions/static/css/pages/auction.css @@ -1,637 +1,583 @@ -/* Auction Details Main Container */ +/* ================================= + AUCTION DETAIL PAGE STYLES + ================================= */ + +/* Main container for auction details */ .auction-details { - max-width: 1200px; - margin: 0 auto; - animation: fadeIn 0.5s ease-out; + max-width: 1200px; + margin: 0 auto; + animation: fadeIn 0.5s ease-out; } -/* Back Button */ +/* Back navigation button */ .back-button { - font-weight: 500; - border-radius: 8px; - display: inline-flex; - align-items: center; - transition: all 0.3s ease; - color: var(--text-secondary); - border-color: var(--border-color); + font-weight: var(--font-medium); + border-radius: var(--radius-lg); + display: inline-flex; + align-items: center; + transition: var(--transition-all); + color: var(--text-secondary); + border-color: var(--border-medium); } .back-button:hover { - transform: translateX(-5px); - background-color: var(--bg-hover); - color: var(--primary-color); + transform: translateX(-5px); + background-color: var(--primary-50); + color: var(--primary-500); } -/* Main Card */ +/* Main card holding auction info */ .auction-main { - border-radius: 15px; - overflow: hidden; - background: var(--card-bg); - border: 1px solid var(--border-light); - box-shadow: var(--shadow-lg); + border-radius: var(--radius-2xl); + overflow: hidden; + background: var(--bg-elevated); + border: 1px solid var(--border-light); + box-shadow: var(--shadow-lg); } -/* Image Container */ +/* Image container */ .auction-image-container { - position: relative; - overflow: hidden; - align-content: center; + position: relative; + overflow: hidden; + align-content: center; } .auction-detail-image { - width: 100%; - height: 600px; - object-fit: cover; - object-position: center; - transition: all 0.5s ease; - padding-left: 1.25rem; + width: 100%; + height: 600px; + object-fit: cover; + object-position: center; + transition: all 0.5s ease; + padding-left: var(--space-5); } .auction-detail-image:hover { - transform: scale(1.03); + transform: scale(1.03); } -/* No Image Placeholder */ +/* Placeholder for when no image is available */ .no-image-placeholder { - height: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - color: var(--text-muted); - background-color: var(--bg-secondary); + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: var(--text-muted); + background-color: var(--bg-secondary); } -/* Category Badge */ +/* Category badge on image */ .category-badge { - position: absolute; - bottom: 15px; - left: 15px; - z-index: 2; + position: absolute; + bottom: var(--space-4); + left: var(--space-4); + z-index: 2; } .category-badge .badge { - background: var(--bg-main-form); - color: var(--text-primary); - padding: 0.5rem 1rem; - font-size: 0.9rem; - border-radius: 20px; - box-shadow: var(--shadow-md); - backdrop-filter: blur(5px); + background: var(--bg-elevated); + color: var(--text-primary); + padding: var(--space-2) var(--space-4); + font-size: var(--text-sm); + border-radius: var(--radius-full); + box-shadow: var(--shadow-md); + backdrop-filter: blur(5px); } -/* Auction Content Section */ +/* Main content area next to image */ .auction-content { - height: 100%; - display: flex; - flex-direction: column; - padding: 2rem !important; + height: 100%; + display: flex; + flex-direction: column; + padding: var(--space-8) !important; } -/* Status Badges */ +/* Status badges (e.g., Active, Closed) */ .status-badges { - display: flex; - flex-wrap: wrap; - gap: 0.5rem; - margin-bottom: 1.5rem; + display: flex; + flex-wrap: wrap; + gap: var(--space-2); + margin-bottom: var(--space-6); } -/* Title */ +/* Auction title */ .auction-title { - font-size: 2rem; - font-weight: 700; - color: var(--text-primary); - margin: 0; - line-height: 1.3; - margin-right: 1rem; + font-size: var(--text-3xl); + font-weight: var(--font-bold); + color: var(--text-primary); + margin: 0; + line-height: var(--leading-tight); + margin-right: var(--space-4); } -/* Auction Actions */ +/* Container for action buttons */ .auction-actions { - margin-left: auto; + margin-left: auto; } -/* Price Section */ +/* Price display section */ .price-section { - background: linear-gradient(145deg, var(--bg-secondary), var(--bg-main)); - border-radius: 15px; - padding: 1.5rem; - margin-bottom: 1.5rem; - border: 1px solid var(--border-light); - box-shadow: var(--shadow-sm); + background: linear-gradient(145deg, var(--bg-secondary), var(--bg-primary)); + border-radius: var(--radius-2xl); + padding: var(--space-6); + margin-bottom: var(--space-6); + border: 1px solid var(--border-light); + box-shadow: var(--shadow-sm); } .current-price { - margin-bottom: 1rem; - position: relative; + margin-bottom: var(--space-4); + position: relative; } .price-label { - display: block; - color: var(--text-muted); - font-size: 0.9rem; - margin-bottom: 0.25rem; + display: block; + color: var(--text-muted); + font-size: var(--text-sm); + margin-bottom: var(--space-1); } .price-amount { - font-size: 2.25rem; - font-weight: 700; - color: var(--price-color); - display: inline-block; + font-size: var(--text-4xl); + font-weight: var(--font-bold); + color: var(--success); + display: inline-block; } .starting-amount { - font-size: 1.25rem; - font-weight: 600; - color: var(--text-secondary); + font-size: var(--text-lg); + font-weight: var(--font-semibold); + color: var(--text-secondary); } .bids-count { - font-size: 0.9rem; - color: var(--bid-color); - font-weight: 600; - background: var(--bg-hover); - padding: 0.25rem 0.75rem; - border-radius: 20px; - margin-left: 1rem; - vertical-align: middle; + font-size: var(--text-sm); + color: var(--bid-current); + font-weight: var(--font-semibold); + background: var(--primary-50); + padding: var(--space-1) var(--space-3); + border-radius: var(--radius-full); + margin-left: var(--space-4); + vertical-align: middle; } -/* Bid Form */ +/* Bid submission form */ .bid-form { - background-color: var(--bg-secondary); - padding: 1.5rem; - border-radius: 12px; - margin-bottom: 1.5rem; - border: 1px solid var(--border-color); + background-color: var(--bg-secondary); + padding: var(--space-6); + border-radius: var(--radius-xl); + margin-bottom: var(--space-6); + border: 1px solid var(--border-medium); } .form-label { - color: var(--text-primary); - margin-bottom: 0.75rem; + color: var(--text-primary); + margin-bottom: var(--space-3); } .input-group { - border-radius: 8px; - overflow: hidden; - box-shadow: var(--shadow-sm); + border-radius: var(--radius-lg); + overflow: hidden; + box-shadow: var(--shadow-sm); } .input-group-text { - background-color: var(--bg-main); - border: 2px solid var(--primary-color); - border-right: none; - color: var(--primary-color); - font-weight: 600; + background-color: var(--bg-primary); + border: 2px solid var(--primary-500); + border-right: none; + color: var(--primary-500); + font-weight: var(--font-semibold); } .form-control { - border: 2px solid var(--primary-color); - border-left: none; - border-right: none; - padding: 0.75rem 1rem; - color: var(--text-primary); - background-color: var(--bg-main-form); + border: 2px solid var(--primary-500); + border-left: none; + border-right: none; + padding: var(--space-3) var(--space-4); + color: var(--text-primary); + background-color: var(--bg-secondary); } .form-control:focus { - box-shadow: none; - border-color: var(--primary-color); - background-color: var(--bg-main); + box-shadow: none; + border-color: var(--primary-500); + background-color: var(--bg-primary); } .form-text { - color: var(--text-muted); - font-size: 0.875rem; + color: var(--text-muted); + font-size: var(--text-sm); } .bid-button { - padding-left: 1.5rem; - padding-right: 1.5rem; - border-radius: 0 8px 8px 0; + padding-left: var(--space-6); + padding-right: var(--space-6); + border-radius: 0 var(--radius-lg) var(--radius-lg) 0; } -/* Owner Actions */ +/* Actions for the auction owner */ .owner-actions { - margin-top: 1rem; - padding: 1.5rem; - background-color: var(--bg-secondary); - border-radius: 12px; - border: 1px solid var(--border-color); + margin-top: var(--space-4); + padding: var(--space-6); + background-color: var(--bg-secondary); + border-radius: var(--radius-xl); + border: 1px solid var(--border-medium); } .owner-note { - color: var(--text-secondary); - font-size: 0.875rem; - text-align: center; + color: var(--text-secondary); + font-size: var(--text-sm); + text-align: center; } -/* Winner Banner */ +/* Banner for the winner */ .winner-banner { - background: linear-gradient(to right, var(--warning-color), #f97316); - color: var(--text-primary); - padding: 1rem; - border-radius: 12px; - font-weight: 500; - text-align: center; - box-shadow: var(--shadow-md); - animation: pulse 2s infinite; + background: linear-gradient(to right, var(--warning), #f97316); + color: var(--text-primary); + padding: var(--space-4); + border-radius: var(--radius-xl); + font-weight: var(--font-medium); + text-align: center; + box-shadow: var(--shadow-md); + animation: pulse 2s infinite; } -/* Info Grid */ +/* Grid for extra info (seller, date, etc.) */ .info-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); - gap: 1rem; - margin-top: 2rem; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: var(--space-4); + margin-top: var(--space-8); } .info-item { - display: flex; - align-items: center; - gap: 0.75rem; - color: var(--text-secondary); - padding: 0.75rem; - background: var(--bg-secondary); - border-radius: 10px; - border: 1px solid var(--border-light); + display: flex; + align-items: center; + gap: var(--space-3); + color: var(--text-secondary); + padding: var(--space-3); + background: var(--bg-secondary); + border-radius: var(--radius-lg); + border: 1px solid var(--border-light); } .info-item i { - color: var(--primary-color); + color: var(--primary-500); } -/* Description Card */ +/* Card for item description */ .description-card { - border-radius: 15px; - border: 1px solid var(--border-light); - overflow: hidden; + border-radius: var(--radius-2xl); + border: 1px solid var(--border-light); + overflow: hidden; } .description-card .card-title { - color: var(--text-primary); - font-size: 1.5rem; - font-weight: 600; - margin-bottom: 1.25rem; - border-bottom: 1px solid var(--border-light); - padding-bottom: 1rem; + color: var(--text-primary); + font-size: var(--text-xl); + font-weight: var(--font-semibold); + margin-bottom: var(--space-5); + border-bottom: 1px solid var(--border-light); + padding-bottom: var(--space-4); } .description-card .card-text { - color: var(--text-primary); - line-height: 1.7; - font-size: 1.05rem; + color: var(--text-primary); + line-height: var(--leading-relaxed); + font-size: 1.05rem; } -/* Comments Section */ +/* Comments section */ .comments-section { - border-radius: 15px; - border: 1px solid var(--border-light); + border-radius: var(--radius-2xl); + border: 1px solid var(--border-light); } .comments-title { - font-size: 1.5rem; - font-weight: 700; - color: var(--text-primary); - margin-bottom: 1.25rem; - border-bottom: 1px solid var(--border-light); - padding-bottom: 1rem; + font-size: var(--text-xl); + font-weight: var(--font-bold); + color: var(--text-primary); + margin-bottom: var(--space-5); + border-bottom: 1px solid var(--border-light); + padding-bottom: var(--space-4); } .comment-card { - background: var(--bg-secondary); - border-radius: 10px; - padding: 1.25rem; - margin-bottom: 1rem; - border: 1px solid var(--border-light); - transition: transform 0.3s ease; + background: var(--bg-secondary); + border-radius: var(--radius-lg); + padding: var(--space-5); + margin-bottom: var(--space-4); + border: 1px solid var(--border-light); + transition: var(--transition-normal); } .comment-card:hover { - transform: translateY(-3px); - box-shadow: var(--shadow-sm); + transform: translateY(-3px); + box-shadow: var(--shadow-sm); } .comment-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 0.75rem; - color: var(--text-secondary); + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--space-3); + color: var(--text-secondary); } .comment-user { - display: flex; - align-items: center; - gap: 0.5rem; - font-weight: 600; + display: flex; + align-items: center; + gap: var(--space-2); + font-weight: var(--font-semibold); } .comment-user i { - color: var(--primary-color); + color: var(--primary-500); } .comment-date { - font-size: 0.85rem; + font-size: var(--text-sm); color: var(--text-muted); - background-color: var(--bg-secondary); - padding: 0.25rem 0.5rem; - border-radius: 4px; + background-color: var(--bg-tertiary); + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-md); display: inline-flex; align-items: center; gap: 0.35rem; - transition: all 0.3s ease; + transition: var(--transition-all); } .comment-date:hover { - background-color: var(--bg-hover); - color: var(--primary-color); + background-color: var(--primary-100); + color: var(--primary-600); } .comment-date i { - color: var(--primary-color); + color: var(--primary-500); font-size: 0.8rem; } .comment-body { - color: var(--text-primary); - line-height: 1.6; + color: var(--text-primary); + line-height: var(--leading-normal); } -/* Empty Comments */ +/* Placeholder for when there are no comments */ .empty-comments { - text-align: center; - padding: 3rem; - color: var(--text-muted); - background-color: var(--bg-secondary); - border-radius: 12px; - border: 1px dashed var(--border-color); + text-align: center; + padding: var(--space-12); + color: var(--text-muted); + background-color: var(--bg-secondary); + border-radius: var(--radius-xl); + border: 1px dashed var(--border-medium); } .empty-comments i { - font-size: 3rem; - margin-bottom: 1rem; - opacity: 0.7; + font-size: var(--text-5xl); + margin-bottom: var(--space-4); + opacity: 0.7; } -/* Comment Form */ +/* Form for adding a new comment */ .comment-form { - margin-bottom: 2rem; + margin-bottom: var(--space-8); } .comment-form textarea { - background: var(--bg-secondary); - border: 2px solid var(--border-color); - border-radius: 12px; - padding: 1rem; - min-height: 120px; - color: var(--text-primary); - font-size: 0.95rem; - line-height: 1.5; - transition: all 0.3s ease; - resize: none; + background: var(--bg-secondary); + border: 2px solid var(--border-medium); + border-radius: var(--radius-xl); + padding: var(--space-4); + min-height: 120px; + color: var(--text-primary); + font-size: var(--text-base); + line-height: var(--leading-normal); + transition: var(--transition-all); + resize: none; } .comment-form textarea:focus { - border-color: var(--primary-color); - box-shadow: 0 0 0 0.25rem rgba(15, 91, 171, 0.15); - background: var(--bg-main); + border-color: var(--border-focus); + box-shadow: 0 0 0 0.25rem rgba(from var(--primary-500) r g b / 0.15); + background: var(--bg-primary); } .comment-form textarea::placeholder { - color: var(--text-muted); - opacity: 0.8; + color: var(--text-muted); + opacity: 0.8; } -/* Post Comment Button */ +/* Post comment button */ .comment-form .btn-primary { - background: var(--gradient-primary); - border: none; - padding: 0.8rem 1.5rem; - font-weight: 500; - border-radius: 10px; - transition: all 0.3s ease; - display: inline-flex; - align-items: center; - gap: 0.5rem; + background: var(--nav-gradient); + border: none; + padding: var(--space-3) var(--space-6); + font-weight: var(--font-medium); + border-radius: var(--radius-lg); + transition: var(--transition-all); + display: inline-flex; + align-items: center; + gap: var(--space-2); } .comment-form .btn-primary:hover { - transform: translateY(-2px); - box-shadow: var(--shadow-md); + transform: translateY(-2px); + box-shadow: var(--shadow-md); } -/* Watchlist Button */ +/* Watchlist button styling */ .btn-outline-heart { - color: var(--danger-color); - border: 2px solid var(--danger-color); - background: transparent; - padding: 0.6rem 1.2rem; - border-radius: 25px; - font-weight: 500; - display: inline-flex; - align-items: center; - gap: 0.5rem; - transition: all 0.3s ease; + color: var(--error); + border: 2px solid var(--error); + background: transparent; + padding: var(--space-2) var(--space-5); + border-radius: var(--radius-full); + font-weight: var(--font-medium); + display: inline-flex; + align-items: center; + gap: var(--space-2); + transition: var(--transition-all); } .btn-outline-heart i { - font-size: 1.1rem; - transition: transform 0.3s ease; + font-size: 1.1rem; + transition: transform 0.3s ease; } .btn-outline-heart:hover { - background: var(--danger-color); - color: white; - transform: translateY(-2px); - box-shadow: 0 5px 15px rgba(231, 76, 60, 0.3); + background: var(--error); + color: var(--white); + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(from var(--error) r g b / 0.3); } .btn-outline-heart:hover i { - transform: scale(1.2); + transform: scale(1.2); } -/* Active State (When in Watchlist) */ +/* Active state for watchlist button */ .btn-outline-heart.active { - background: var(--danger-color); - color: white; + background: var(--error); + color: var(--white); } .btn-outline-heart.active:hover { - background: #c0392b; - border-color: #c0392b; + background: var(--error-dark); + border-color: var(--error-dark); } -/* Dark Mode Support */ -[data-theme='dark'] .auction-main, -[data-theme='dark'] .description-card, -[data-theme='dark'] .comments-section { - background-color: var(--card-bg); - border-color: var(--border-color); -} +/* Dark Mode Overrides are handled in variables.css */ -[data-theme='dark'] .price-section { - background: linear-gradient( - 145deg, - rgba(30, 41, 59, 0.8), - rgba(15, 23, 42, 0.8) - ); - border-color: var(--border-color); -} +/* Responsive Design */ +@media (max-width: 992px) { + .auction-image-container { + height: 400px; + } -[data-theme='dark'] .input-group-text { - background-color: rgba(59, 130, 246, 0.2); - border-color: var(--primary-color); - color: var(--primary-light); -} + .auction-content { + padding: var(--space-6) !important; + } -[data-theme='dark'] .form-control { - background-color: var(--bg-secondary-form); - border-color: var(--primary-color); - color: var(--text-primary); -} + .auction-title { + font-size: var(--text-2xl); + } -[data-theme='dark'] .bid-form, -[data-theme='dark'] .owner-actions, -[data-theme='dark'] .info-item, -[data-theme='dark'] .comment-card, -[data-theme='dark'] .empty-comments { - background-color: rgba(30, 41, 59, 0.5); - border-color: var(--border-color); + .price-amount { + font-size: var(--text-2xl); + } } -[data-theme='dark'] .winner-banner { - background: linear-gradient(to right, #d97706, #c2410c); - color: white; -} +@media (max-width: 768px) { + .auction-image-container { + align-content: normal; + object-fit: cover; + } -[data-theme='dark'] .btn-outline-heart { - border-color: #e74c3c; - color: #e74c3c; -} + .info-grid { + grid-template-columns: 1fr; + } -[data-theme='dark'] .btn-outline-heart:hover, -[data-theme='dark'] .btn-outline-heart.active { - background: #e74c3c; - color: #ecf0f1; -} + .comment-header { + flex-direction: column; + align-items: flex-start; + gap: var(--space-2); + } -[data-theme='dark'] .btn-outline-heart.active:hover { - background: #c0392b; - border-color: #c0392b; - color: #ecf0f1; + .comment-form textarea { + min-height: 100px; + font-size: var(--text-sm); + padding: var(--space-3); + } + + .comment-form .btn-primary { + width: 100%; + justify-content: center; + padding: var(--space-3); + } + .auction-detail-image{ + padding-left: 0; + } } -/* Responsive Design */ -@media (max-width: 992px) { - .auction-image-container { - height: 400px; - } +@media (max-width: 576px) { + .auction-image-container { + object-fit: cover; + } - .auction-content { - padding: 1.5rem !important; - } + .auction-content { + padding: var(--space-4) !important; + } - .auction-title { - font-size: 1.75rem; - } + .auction-title { + font-size: var(--text-xl); + margin-bottom: var(--space-2); + } - .price-amount { - font-size: 1.75rem; - } -} + .btn-outline-heart { + padding: var(--space-2) var(--space-4); + font-size: var(--text-sm); + } -@media (max-width: 768px) { - .auction-image-container { - align-content: normal; - object-fit: cover; - } - - .info-grid { - grid-template-columns: 1fr; - } - - .comment-header { - flex-direction: column; - align-items: flex-start; - gap: 0.5rem; - } - - .comment-form textarea { - min-height: 100px; - font-size: 0.9rem; - padding: 0.75rem; - } - - .comment-form .btn-primary { - width: 100%; - justify-content: center; - padding: 0.75rem; - } - .auction-detail-image{ - padding-left: 0; - } -} + .watchlist-text { + display: none; + } -@media (max-width: 576px) { - .auction-image-container { - object-fit: cover; - } - - .auction-content { - padding: 1rem !important; - } - - .auction-title { - font-size: 1.5rem; - margin-bottom: 0.5rem; - } - - .btn-outline-heart { - padding: 0.5rem 1rem; - font-size: 0.9rem; - } - - .watchlist-text { - display: none; - } - - .btn-outline-heart i { - margin: 0; - font-size: 1rem; - } - - .price-section, - .bid-form, - .owner-actions { - padding: 1rem; - border-radius: 10px; - } - - .price-amount { - font-size: 1.5rem; - } - - .bids-count { - display: block; - margin: 0.5rem 0 0 0; - } - - .comments-title, - .card-title { - font-size: 1.25rem; - } - - .empty-comments { - padding: 2rem 1rem; - } - - .empty-comments i { - font-size: 2.5rem; - } - - .comment-date { - font-size: 0.75rem; + .btn-outline-heart i { + margin: 0; + font-size: 1rem; + } + + .price-section, + .bid-form, + .owner-actions { + padding: var(--space-4); + border-radius: var(--radius-lg); + } + + .price-amount { + font-size: var(--text-xl); + } + + .bids-count { + display: block; + margin: var(--space-2) 0 0 0; + } + + .comments-title, + .card-title { + font-size: var(--text-lg); + } + + .empty-comments { + padding: var(--space-8) var(--space-4); + } + + .empty-comments i { + font-size: var(--text-4xl); + } + + .comment-date { + font-size: var(--text-xs); padding: 0.2rem 0.4rem; } } diff --git a/auctions/static/css/pages/categories.css b/auctions/static/css/pages/categories.css index 256cb72..deeafb1 100644 --- a/auctions/static/css/pages/categories.css +++ b/auctions/static/css/pages/categories.css @@ -1,376 +1,352 @@ -/* Categories Page Styles */ +/* ================================= + CATEGORIES PAGE STYLES + ================================= */ + .categories-container { - min-height: calc(100vh - 120px); - padding: 2rem 0; - animation: fadeIn 0.5s ease-out; + min-height: calc(100vh - 120px); + padding: var(--space-8) 0; + animation: fadeIn 0.5s ease-out; } -/* Header Styles with improved hierarchy */ +/* Header styles */ .display-4 { - font-size: 2.5rem; - font-weight: 700; - color: var(--text-primary); - margin-bottom: 1rem; - position: relative; + font-size: var(--text-4xl); + font-weight: var(--font-bold); + color: var(--text-primary); + margin-bottom: var(--space-4); + position: relative; } .text-gradient { - background: var(--gradient-primary); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; + background: var(--nav-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; } .lead { - color: var(--text-secondary); - font-size: 1.2rem; - margin-bottom: 0; - max-width: 600px; - margin-left: auto; - margin-right: auto; + color: var(--text-secondary); + font-size: var(--text-lg); + margin-bottom: 0; + max-width: 600px; + margin-left: auto; + margin-right: auto; } -/* Category Grid - improved visual layout */ +/* Category grid */ .category-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); - gap: 1.5rem; - margin-bottom: 3rem; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: var(--space-6); + margin-bottom: var(--space-12); } .category-card { - background: var(--card-bg); - border-radius: 15px; - transition: all 0.4s ease; - position: relative; - overflow: hidden; - box-shadow: var(--shadow-sm); - border: 1px solid var(--border-light); - height: 100%; + background: var(--bg-elevated); + border-radius: var(--radius-2xl); + transition: var(--transition-slow); + position: relative; + overflow: hidden; + box-shadow: var(--shadow-sm); + border: 1px solid var(--border-light); + height: 100%; } .category-card:hover { - transform: translateY(-5px); - box-shadow: var(--shadow-lg); - border-color: var(--primary-color); + transform: translateY(-5px); + box-shadow: var(--shadow-lg); + border-color: var(--primary-500); } .category-card.active { - background: var(--gradient-primary); - border-color: transparent; - box-shadow: var(--hover-shadow); + background: var(--nav-gradient); + border-color: transparent; + box-shadow: var(--shadow-xl); } .category-link { - display: flex; - flex-direction: column; - align-items: center; - padding: 2rem 1.5rem; - text-decoration: none; - color: var(--text-primary); - height: 100%; - position: relative; + display: flex; + flex-direction: column; + align-items: center; + padding: var(--space-8) var(--space-6); + text-decoration: none; + color: var(--text-primary); + height: 100%; + position: relative; } .category-card.active .category-link { - color: var(--text-light); + color: var(--white); } -/* Better icon presentation */ +/* Category icon */ .category-icon-wrapper { - width: 80px; - height: 80px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; - background: var(--bg-secondary); - margin-bottom: 1.5rem; - transition: all 0.3s ease; + width: 80px; + height: 80px; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--radius-full); + background: var(--bg-secondary); + margin-bottom: var(--space-6); + transition: var(--transition-all); } .category-card.active .category-icon-wrapper { - background: rgba(255, 255, 255, 0.2); + background: rgba(255, 255, 255, 0.2); } .category-card:hover .category-icon-wrapper { - transform: scale(1.1); + transform: scale(1.1); } .category-icon { - font-size: 2.5rem; - color: var(--primary-color); - transition: transform 0.3s ease; + font-size: var(--text-4xl); + color: var(--primary-500); + transition: transform 0.3s ease; } .category-card.active .category-icon { - color: var(--text-light); + color: var(--white); } .category-card:hover .category-icon { - transform: scale(1.1); + transform: scale(1.1); } .category-title { - font-size: 1.2rem; - font-weight: 600; - margin-bottom: 0.75rem; - text-align: center; - transition: color 0.3s ease; + font-size: var(--text-lg); + font-weight: var(--font-semibold); + margin-bottom: var(--space-3); + text-align: center; + transition: color 0.3s ease; } .category-items { - font-size: 0.9rem; - color: var(--text-muted); - text-align: center; - display: flex; - align-items: center; - transition: all 0.3s ease; + font-size: var(--text-sm); + color: var(--text-muted); + text-align: center; + display: flex; + align-items: center; + transition: var(--transition-all); } .category-card:hover .category-items { - color: var(--primary-color); + color: var(--primary-500); } .category-card.active .category-items { - color: rgba(255, 255, 255, 0.9); + color: rgba(255, 255, 255, 0.9); } -/* Results Section styling */ +/* Results section */ .results-section { - background: var(--card-bg); - border-radius: 15px; - padding: 2rem; - margin-bottom: 2rem; - box-shadow: var(--shadow-md); - border: 1px solid var(--border-light); + background: var(--bg-elevated); + border-radius: var(--radius-2xl); + padding: var(--space-8); + margin-bottom: var(--space-8); + box-shadow: var(--shadow-md); + border: 1px solid var(--border-light); } .results-header { - display: flex; - justify-content: space-between; - align-items: center; - padding-bottom: 1rem; - border-bottom: 2px solid var(--border-color); - margin-bottom: 2rem; + display: flex; + justify-content: space-between; + align-items: center; + padding-bottom: var(--space-4); + border-bottom: 2px solid var(--border-medium); + margin-bottom: var(--space-8); } .results-title { - font-size: 1.5rem; - font-weight: 600; - color: var(--text-primary); - margin: 0; + font-size: var(--text-xl); + font-weight: var(--font-semibold); + color: var(--text-primary); + margin: 0; } .btn-outline-secondary { - color: var(--text-secondary); - border-color: var(--border-color); - background-color: transparent; - transition: all 0.3s ease; + color: var(--text-secondary); + border-color: var(--border-medium); + background-color: transparent; + transition: var(--transition-all); } .btn-outline-secondary:hover { - background-color: var(--bg-hover); - color: var(--primary-color); - transform: translateY(-2px); + background-color: var(--primary-50); + color: var(--primary-500); + transform: translateY(-2px); } -/* Empty State - more engaging */ +/* Empty state */ .empty-category-state { - padding: 4rem 2rem; - text-align: center; + padding: var(--space-16) var(--space-8); + text-align: center; } .empty-content { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - animation: fadeIn 0.5s ease-out; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + animation: fadeIn 0.5s ease-out; } .empty-content i { - font-size: 4rem; - color: var(--text-muted); - margin-bottom: 1.5rem; - opacity: 0.7; + font-size: var(--text-6xl); + color: var(--text-muted); + margin-bottom: var(--space-6); + opacity: 0.7; } .empty-content h3 { - color: var(--text-primary); - font-size: 1.75rem; - font-weight: 600; - margin-bottom: 1rem; + color: var(--text-primary); + font-size: var(--text-2xl); + font-weight: var(--font-semibold); + margin-bottom: var(--space-4); } .empty-content p { - color: var(--text-secondary); - font-size: 1.1rem; - margin-bottom: 2rem; - max-width: 500px; - margin-left: auto; - margin-right: auto; + color: var(--text-secondary); + font-size: var(--text-lg); + margin-bottom: var(--space-8); + max-width: 500px; + margin-left: auto; + margin-right: auto; } .empty-actions { - margin-top: 1rem; + margin-top: var(--space-4); } .empty-actions a { - display: flex; - align-items: anchor-center; + display: flex; + align-items: center; } .empty-actions i { - margin-bottom: 0; - margin-right: 0.5rem; + margin-bottom: 0; + margin-right: var(--space-2); } .listing-item { - margin-bottom: 1.5rem; -} - -/* Dark Mode Support */ -[data-theme='dark'] .category-card { - background: var(--card-bg); - border-color: var(--border-color); -} - -[data-theme='dark'] .category-icon-wrapper { - background: rgba(255, 255, 255, 0.05); -} - -[data-theme='dark'] .category-card:hover { - border-color: var(--primary-light); -} - -[data-theme='dark'] .results-section { - background: var(--card-bg); - border-color: var(--border-color); + margin-bottom: var(--space-6); } -[data-theme='dark'] .btn-outline-secondary { - color: var(--text-secondary); - border-color: var(--border-color); -} - -[data-theme='dark'] .btn-outline-secondary:hover { - background-color: rgba(255, 255, 255, 0.1); - color: var(--primary-light); -} +/* Dark Mode Support is handled by variables.css */ /* Responsive Design */ @media (max-width: 768px) { - .categories-container { - padding: 1.5rem 0; - } + .categories-container { + padding: var(--space-6) 0; + } - .display-4 { - font-size: 2rem; - } + .display-4 { + font-size: var(--text-3xl); + } - .lead { - font-size: 1rem; - } + .lead { + font-size: var(--text-base); + } - .category-grid { - grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); - gap: 1rem; - } + .category-grid { + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: var(--space-4); + } - .category-link { - padding: 1.5rem 1rem; - } + .category-link { + padding: var(--space-6) var(--space-4); + } - .category-icon-wrapper { - width: 70px; - height: 70px; - margin-bottom: 1rem; - } + .category-icon-wrapper { + width: 70px; + height: 70px; + margin-bottom: var(--space-4); + } - .category-icon { - font-size: 2rem; - } + .category-icon { + font-size: var(--text-3xl); + } - .category-title { - font-size: 1rem; - margin-bottom: 0.5rem; - } + .category-title { + font-size: var(--text-base); + margin-bottom: var(--space-2); + } - .category-items { - font-size: 0.8rem; - } + .category-items { + font-size: var(--text-xs); + } - .results-section { - padding: 1.5rem; - } + .results-section { + padding: var(--space-6); + } - .results-header { - flex-direction: column; - gap: 1rem; - align-items: flex-start; - } + .results-header { + flex-direction: column; + gap: var(--space-4); + align-items: flex-start; + } - .empty-content i { - font-size: 3rem; - } + .empty-content i { + font-size: var(--text-5xl); + } - .empty-content h3 { - font-size: 1.5rem; - } + .empty-content h3 { + font-size: var(--text-xl); + } - .empty-content p { - font-size: 1rem; - } + .empty-content p { + font-size: var(--text-base); + } } @media (max-width: 576px) { - .categories-container { - padding: 1rem 0; - } - - .display-4 { - font-size: 1.75rem; - } - - .lead { - font-size: 0.95rem; - } - - .category-grid { - grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); - gap: 0.75rem; - } - - .category-link { - padding: 1.25rem 0.75rem; - } - - .category-icon-wrapper { - width: 60px; - height: 60px; - margin-bottom: 0.75rem; - } - - .category-icon { - font-size: 1.75rem; - } - - .results-section { - padding: 1.25rem; - border-radius: 10px; - } - - .empty-category-state { - padding: 2rem 1rem; - } - - .empty-content h3 { - font-size: 1.25rem; - } - - .empty-content p { - font-size: 0.9rem; - margin-bottom: 1.5rem; - } + .categories-container { + padding: var(--space-4) 0; + } + + .display-4 { + font-size: var(--text-2xl); + } + + .lead { + font-size: var(--text-sm); + } + + .category-grid { + grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); + gap: var(--space-3); + } + + .category-link { + padding: var(--space-5) var(--space-3); + } + + .category-icon-wrapper { + width: 60px; + height: 60px; + margin-bottom: var(--space-3); + } + + .category-icon { + font-size: var(--text-2xl); + } + + .results-section { + padding: var(--space-5); + border-radius: var(--radius-lg); + } + + .empty-category-state { + padding: var(--space-8) var(--space-4); + } + + .empty-content h3 { + font-size: var(--text-lg); + } + + .empty-content p { + font-size: var(--text-sm); + margin-bottom: var(--space-6); + } } diff --git a/auctions/static/css/pages/error-pages.css b/auctions/static/css/pages/error-pages.css new file mode 100644 index 0000000..74b83ad --- /dev/null +++ b/auctions/static/css/pages/error-pages.css @@ -0,0 +1,265 @@ +/* ======================================== + ERROR PAGES STYLES + ======================================== */ + +#main-content { + padding: 0; +} + +/* Base Error Container */ +.error-container { + display: flex; + align-items: center; + justify-content: center; + position: relative; + overflow: hidden; + box-sizing: border-box; + min-height: 100vh; +} + +/* Floating Elements for decoration */ +.floating-elements { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + pointer-events: none; + overflow: hidden; +} + +.floating-element { + position: absolute; + opacity: 0.1; + animation: float 6s ease-in-out infinite; +} + +.floating-element:nth-child(1) { top: 20%; left: 10%; animation-delay: 0s; } +.floating-element:nth-child(2) { top: 60%; right: 10%; animation-delay: 2s; } +.floating-element:nth-child(3) { bottom: 20%; left: 20%; animation-delay: 4s; } + +@keyframes float { + 0%, 100% { transform: translateY(0px) rotate(0deg); } + 50% { transform: translateY(-20px) rotate(180deg); } +} + +/* Error Card */ +.error-card { + background: var(--bg-elevated); + border-radius: var(--radius-3xl); + padding: var(--space-10) var(--space-8); + box-shadow: var(--shadow-2xl); + text-align: center; + max-width: 90vw; + width: 100%; + max-height: 90vh; + position: relative; + overflow: hidden; + display: flex; + flex-direction: column; + justify-content: center; + box-sizing: border-box; +} + +.error-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 5px; + background: var(--error-gradient, linear-gradient(90deg, var(--error), var(--warning), var(--info), var(--primary-400))); +} + +/* Error Code (e.g., 404) */ +.error-code { + font-size: clamp(4rem, 12vw, 8rem); + font-weight: var(--font-extrabold); + background: var(--error-gradient, linear-gradient(135deg, var(--error), var(--warning))); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin-bottom: var(--space-4); + line-height: 0.8; + text-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +/* Error Title */ +.error-title { + font-size: clamp(1.5rem, 4vw, 2.5rem); + font-weight: var(--font-bold); + color: var(--text-primary); + margin-bottom: var(--space-4); + line-height: var(--leading-tight); +} + +/* Error Message */ +.error-message { + font-size: clamp(1rem, 2.5vw, 1.2rem); + color: var(--text-secondary); + margin-bottom: var(--space-6); + line-height: var(--leading-relaxed); + max-width: 500px; + margin-left: auto; + margin-right: auto; +} + +/* Error Icon */ +.error-icon { + font-size: clamp(2.5rem, 6vw, 4rem); + color: var(--error-icon-color, var(--primary-500)); + margin-bottom: var(--space-5); +} + +/* Icon Animations */ +.error-icon.bounce { animation: bounce 2s infinite; } +.error-icon.rotate { animation: rotate 2s linear infinite; } +.error-icon.pulse { animation: pulse 2s infinite; } +.error-icon.shake { animation: shake 1s infinite; } + +@keyframes bounce { 0%, 20%, 50%, 80%, 100% { transform: translateY(0); } 40% { transform: translateY(-10px); } 60% { transform: translateY(-5px); } } +@keyframes rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } +@keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.1); } 100% { transform: scale(1); } } +@keyframes shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-5px); } 75% { transform: translateX(5px); } } + +/* Buttons */ +.btn-custom { + background: var(--error-gradient, linear-gradient(135deg, var(--primary-500), var(--primary-600))); + border: none; + color: var(--white); + padding: var(--space-3) var(--space-6); + border-radius: var(--radius-full); + font-weight: var(--font-semibold); + text-decoration: none; + display: inline-block; + margin: var(--space-2); + transition: var(--transition-all); + box-shadow: var(--shadow-lg); + font-size: clamp(0.9rem, 2vw, 1rem); +} + +.btn-custom:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-xl); + color: var(--white); +} + +.btn-outline-custom { + background: transparent; + border: 2px solid var(--error-button-color, var(--primary-500)); + color: var(--error-button-color, var(--primary-500)); + padding: var(--space-3) var(--space-6); + border-radius: var(--radius-full); + font-weight: var(--font-semibold); + text-decoration: none; + display: inline-block; + margin: var(--space-2); + transition: var(--transition-all); + font-size: clamp(0.9rem, 2vw, 1rem); +} + +.btn-outline-custom:hover { + background: var(--error-button-color, var(--primary-500)); + color: var(--white); + transform: translateY(-2px); +} + +/* Suggestions Box */ +.suggestions { + background: var(--bg-secondary); + border-radius: var(--radius-xl); + padding: var(--space-5); + margin-top: var(--space-5); + text-align: left; + max-height: 200px; + overflow-y: auto; +} + +.suggestions h5 { + color: var(--text-primary); + margin-bottom: var(--space-4); + font-weight: var(--font-semibold); + font-size: clamp(1rem, 2.5vw, 1.2rem); +} + +.suggestions ul { + list-style: none; + padding: 0; +} + +.suggestions li { + padding: var(--space-2) 0; + border-bottom: 1px solid var(--border-light); + color: var(--text-secondary); + font-size: clamp(0.85rem, 2vw, 1rem); +} + +.suggestions li:last-child { + border-bottom: none; +} + +.suggestions li i { + color: var(--error-icon-color, var(--primary-500)); + margin-right: var(--space-3); + width: 20px; +} + +/* Debug Info Box */ +.debug-info { + background: var(--gray-800); + color: var(--gray-200); + border-radius: var(--radius-lg); + padding: var(--space-5); + margin-top: var(--space-8); + font-family: var(--font-mono); + font-size: var(--text-sm); + text-align: left; + max-height: 300px; + overflow-y: auto; +} + +.debug-info h6 { + color: var(--primary-400); + margin-bottom: var(--space-4); + font-weight: var(--font-semibold); +} + +.debug-info pre { + margin: 0; + white-space: pre-wrap; + word-wrap: break-word; +} + +/* ======================================== + SPECIFIC ERROR PAGE THEMES + ======================================== */ + +.error-404 { --error-gradient: linear-gradient(135deg, var(--info), var(--primary-600)); --error-icon-color: var(--info); --error-button-color: var(--info); } +.error-400 { --error-gradient: linear-gradient(135deg, var(--warning), #8e44ad); --error-icon-color: var(--warning); --error-button-color: var(--warning); } +.error-403 { --error-gradient: linear-gradient(135deg, var(--warning-dark), var(--error)); --error-icon-color: var(--warning-dark); --error-button-color: var(--warning-dark); } +.error-500 { --error-gradient: linear-gradient(135deg, var(--error), var(--error-dark)); --error-icon-color: var(--error); --error-button-color: var(--error); } + +.error-404 .error-container { background: linear-gradient(135deg, var(--info) 0%, var(--primary-600) 100%); } +.error-404 .floating-element:nth-child(1) i { color: var(--info); } +.error-404 .floating-element:nth-child(2) i { color: var(--primary-600); } +.error-404 .floating-element:nth-child(3) i { color: var(--primary-400); } + +.error-400 .error-container { background: linear-gradient(135deg, var(--warning) 0%, #8e44ad 100%); } +.error-403 .error-container { background: linear-gradient(135deg, var(--warning-dark) 0%, var(--error) 100%); } +.error-500 .error-container { background: linear-gradient(135deg, var(--error) 0%, var(--error-dark) 100%); } + +/* ======================================== + DARK THEME SUPPORT (Handled via variables.css) + ======================================== */ + +/* Responsive design is handled by clamp() and media queries below */ +@media (max-width: 768px) { + /* Most responsive handled by clamp, minor tweaks here */ +} + +@media (max-width: 480px) { + .btn-custom, + .btn-outline-custom { + min-width: 120px; + } +} diff --git a/auctions/static/css/pages/index.css b/auctions/static/css/pages/index.css index 7107d1d..618ee6e 100644 --- a/auctions/static/css/pages/index.css +++ b/auctions/static/css/pages/index.css @@ -1,416 +1,271 @@ -/* Index Page Styles - Enhanced */ +/* ================================= + INDEX PAGE STYLES + ================================= */ + .listings-grid { - display: grid; - gap: 2rem; - padding: 1rem; - animation: fadeIn 0.5s ease-out forwards; + display: grid; + gap: var(--space-8); + padding: var(--space-4); + animation: fadeIn 0.5s ease-out forwards; } .listing-item { - transition: all 0.3s ease; - position: relative; - overflow: hidden; + transition: var(--transition-all); + position: relative; + overflow: hidden; } .listing-item:hover { - transform: translateY(-5px); - box-shadow: var(--hover-shadow); + transform: translateY(-5px); + box-shadow: var(--shadow-xl); } -/* Header Section */ +/* Header section */ .page-header { - background: linear-gradient( - 135deg, - var(--primary-light), - rgba(255, 255, 255, 0.9) - ); - border-radius: 15px; - padding: 2rem; - margin-bottom: 2rem; - box-shadow: var(--shadow-md); - border: 1px solid var(--border-light); - position: relative; - overflow: hidden; + background: linear-gradient(135deg, var(--primary-50), rgba(255, 255, 255, 0.9)); + border-radius: var(--radius-2xl); + padding: var(--space-8); + margin-bottom: var(--space-8); + box-shadow: var(--shadow-md); + border: 1px solid var(--border-light); + position: relative; + overflow: hidden; } .page-header::after { - content: ''; - position: absolute; - top: 0; - right: 0; - width: 100%; - height: 100%; - opacity: 0.1; - z-index: 0; + content: ''; + position: absolute; + top: 0; + right: 0; + width: 100%; + height: 100%; + opacity: 0.1; + z-index: 0; } .display-4 { - font-size: 2.5rem; - font-weight: 700; - color: var(--primary-dark); - margin-bottom: 1rem; - position: relative; - z-index: 1; + font-size: var(--text-4xl); + font-weight: var(--font-bold); + color: var(--primary-800); + margin-bottom: var(--space-4); + position: relative; + z-index: 1; } .lead { - color: var(--text-secondary); - font-size: 1.2rem; - margin-bottom: 0; - position: relative; - z-index: 1; - max-width: 650px; + color: var(--text-secondary); + font-size: var(--text-lg); + margin-bottom: 0; + position: relative; + z-index: 1; + max-width: 650px; } -/* Create Auction Button */ +/* Create auction button */ .create-auction-btn { - background: var(--gradient-primary); - border: none; - padding: 1rem 2rem; - font-weight: 600; - border-radius: 10px; - transition: all 0.3s ease; - color: var(--text-light); - text-decoration: none; - display: inline-flex; - align-items: center; - gap: 0.75rem; - box-shadow: var(--shadow-sm); + background: var(--nav-gradient); + border: none; + padding: var(--space-4) var(--space-8); + font-weight: var(--font-semibold); + border-radius: var(--radius-lg); + transition: var(--transition-all); + color: var(--white); + text-decoration: none; + display: inline-flex; + align-items: center; + gap: var(--space-3); + box-shadow: var(--shadow-sm); } .create-auction-btn:hover { - transform: translateY(-2px); - box-shadow: var(--hover-shadow); - color: var(--text-light); + transform: translateY(-2px); + box-shadow: var(--shadow-lg); + color: var(--white); } .create-auction-btn i { - transition: transform 0.3s ease; + transition: transform 0.3s ease; } .create-auction-btn:hover i { - transform: rotate(90deg); + transform: rotate(90deg); } -/* Filter Card */ +/* Filter card */ .filter-card { - background: var(--card-bg); - border-radius: 12px; - border: 1px solid var(--border-light); - box-shadow: var(--shadow-sm); - transition: all 0.3s ease; - margin-bottom: 2rem; + background: var(--bg-elevated); + border-radius: var(--radius-xl); + border: 1px solid var(--border-light); + box-shadow: var(--shadow-sm); + transition: var(--transition-all); + margin-bottom: var(--space-8); } .filter-card:hover { - box-shadow: var(--shadow-md); + box-shadow: var(--shadow-md); } .filter-card .form-label { - color: var(--text-secondary); - font-weight: 600; - font-size: 0.9rem; + color: var(--text-secondary); + font-weight: var(--font-semibold); + font-size: var(--text-sm); } .filter-card .form-select, .filter-card .form-control { - background-color: var(--bg-secondary-form); - border: 1px solid var(--border-color); - color: var(--text-primary); - border-radius: 8px; - padding: 0.75rem; - transition: all 0.3s ease; + background-color: var(--bg-secondary); + border: 1px solid var(--border-medium); + color: var(--text-primary); + border-radius: var(--radius-lg); + padding: var(--space-3); + transition: var(--transition-all); } .filter-card .form-select:focus, .filter-card .form-control:focus { - border-color: var(--primary-color); - box-shadow: 0 0 0 0.2rem rgba(15, 91, 171, 0.15); - background-color: var(--bg-main); + border-color: var(--primary-500); + box-shadow: 0 0 0 0.2rem rgba(from var(--primary-500) r g b / 0.15); + background-color: var(--bg-primary); } .filter-card .btn-outline-primary { - color: var(--primary-color); - border-color: var(--primary-color); - border-radius: 8px; - padding: 0.75rem 1.5rem; - font-weight: 600; - transition: all 0.3s ease; + color: var(--primary-500); + border-color: var(--primary-500); + border-radius: var(--radius-lg); + padding: var(--space-3) var(--space-6); + font-weight: var(--font-semibold); + transition: var(--transition-all); } .filter-card .btn-outline-primary:hover { - background-color: var(--primary-color); - color: var(--text-light); - transform: translateY(-2px); - box-shadow: var(--shadow-sm); + background-color: var(--primary-500); + color: var(--white); + transform: translateY(-2px); + box-shadow: var(--shadow-sm); } -/* Empty State */ +/* Empty state */ .empty-state { - text-align: center; - padding: 4rem 2rem; - background: var(--card-bg); - border-radius: 15px; - box-shadow: var(--shadow-md); - border: 1px solid var(--border-light); - animation: fadeIn 0.5s ease-out forwards; + text-align: center; + padding: var(--space-16) var(--space-8); + background: var(--bg-elevated); + border-radius: var(--radius-2xl); + box-shadow: var(--shadow-md); + border: 1px solid var(--border-light); + animation: fadeIn 0.5s ease-out forwards; } .empty-state i { - font-size: 4rem; - color: var(--text-muted); - margin-bottom: 1.5rem; - opacity: 0.5; + font-size: var(--text-6xl); + color: var(--text-muted); + margin-bottom: var(--space-6); + opacity: 0.5; } .empty-state h4 { - color: var(--text-primary); - font-weight: 700; - margin-bottom: 1rem; - font-size: 1.75rem; + color: var(--text-primary); + font-weight: var(--font-bold); + margin-bottom: var(--space-4); + font-size: var(--text-2xl); } .empty-state p { - color: var(--text-secondary); - margin-bottom: 2rem; - max-width: 500px; - margin-left: auto; - margin-right: auto; + color: var(--text-secondary); + margin-bottom: var(--space-8); + max-width: 500px; + margin-left: auto; + margin-right: auto; } .empty-state .btn { - padding: 1rem 2rem; - font-weight: 600; - border-radius: 10px; + padding: var(--space-4) var(--space-8); + font-weight: var(--font-semibold); + border-radius: var(--radius-lg); } /* Recently viewed section */ .recently-viewed-section { - margin-top: 3rem; - padding: 2rem; - background: var(--card-bg); - border-radius: 15px; - box-shadow: var(--shadow-md); - border: 1px solid var(--border-light); + margin-top: var(--space-12); + padding: var(--space-8); + background: var(--bg-elevated); + border-radius: var(--radius-2xl); + box-shadow: var(--shadow-md); + border: 1px solid var(--border-light); } .recently-viewed-title { - font-size: 1.5rem; - color: var(--text-primary); - margin-bottom: 1.5rem; - font-weight: 600; - display: flex; - align-items: center; - gap: 0.75rem; -} - -.card-body { - color: var(--text-primary); + font-size: var(--text-xl); + color: var(--text-primary); + margin-bottom: var(--space-6); + font-weight: var(--font-semibold); + display: flex; + align-items: center; + gap: var(--space-3); } .recently-viewed-title i { - color: var(--primary-color); -} - -/* Form elements dark mode support */ -[data-theme='dark'] .card-body { - background: var(--card-bg); -} - -[data-theme='dark'] .page-header { - background: linear-gradient( - 135deg, - rgba(15, 23, 42, 0.8), - rgba(30, 41, 59, 0.8) - ); - border-color: var(--border-color); -} - -[data-theme='dark'] .display-4 { - color: var(--text-primary); + color: var(--primary-500); } -[data-theme='dark'] .empty-state { - background-color: var(--card-bg); - border-color: var(--border-color); -} - -[data-theme='dark'] .filter-card { - background-color: var(--card-bg); - border-color: var(--border-color); -} +/* Dark mode support is handled by variables.css */ -[data-theme='dark'] .recently-viewed-section { - background-color: var(--card-bg); - border-color: var(--border-color); -} - -[data-theme='dark'] .form-select { - background-color: var(--bg-secondary-form); - border-color: var(--border-color); - color: var(--text-primary); -} - -[data-theme='dark'] .btn-outline-primary { - color: var(--primary-light); - border-color: var(--primary-color); +/* Responsive adjustments */ +@media (min-width: 768px) { + .listings-grid { + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + } } -[data-theme='dark'] .btn-outline-primary:hover { - background-color: var(--primary-color); - color: var(--text-light); - box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4); -} +@media (max-width: 767px) { + .listings-grid { + grid-template-columns: 1fr; + gap: var(--space-5); + } -[data-theme='dark'] .form-label { - color: var(--text-secondary); -} + .page-header { + padding: var(--space-6); + text-align: center; + } -/* Desktop and Tablet (≥768px) */ -@media (min-width: 768px) { - .listings-grid { - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); - } - - .card { - height: 100%; - display: flex; - flex-direction: column; - } - - .auction-card .row { - flex-direction: row; - height: 100%; - } - - .image-container { - border-radius: 0.375rem 0 0 0.375rem; - max-height: 220px; - height: 100%; - object-fit: cover; - } -} + .display-4 { + font-size: var(--text-2xl); + margin-bottom: var(--space-3); + } -/* Mobile (<768px) */ -@media (max-width: 767px) { - .listings-grid { - grid-template-columns: 1fr; - gap: 1.25rem; - } - - .auction-card { - margin-bottom: 1rem; - } - - .auction-card .row { - flex-direction: column; - } - - .col-md-4, - .col-md-8 { - width: 100%; - } - - .image-container { - border-radius: 0.375rem 0.375rem 0 0; - max-height: 200px; - width: 100%; - } - - .card-body { - padding: 1.25rem; - } - - .page-header { - padding: 1.5rem; - text-align: center; - } - - .display-4 { - font-size: 1.75rem; - margin-bottom: 0.75rem; - } - - .lead { - font-size: 1rem; - margin: 0 auto 1.5rem; - } - - .card-title { - font-size: 1.25rem; - } - - .price-tag { - font-size: 1.1rem; - } - - .description-text { - font-size: 0.95rem; - } - - .empty-state { - padding: 3rem 1rem; - } - - .empty-state h4 { - font-size: 1.5rem; - } - - .filter-card .form-select, - .filter-card .form-control { - font-size: 0.95rem; - } + .lead { + font-size: var(--text-base); + margin: 0 auto var(--space-6); + } } -/* Small Mobile (<576px) */ @media (max-width: 576px) { - .listings-grid { - padding: 0.5rem; - gap: 1rem; - } - - .card-body { - padding: 1rem; - } - - .card-title { - font-size: 1.1rem; - } - - .empty-state { - padding: 2rem 1rem; - } - - .empty-state i { - font-size: 3rem; - } - - .empty-state h4 { - font-size: 1.25rem; - } - - .empty-state p { - font-size: 0.95rem; - } - - .filter-card { - padding: 0.75rem; - } - - .page-header { - padding: 1.25rem; - } - - .display-4 { - font-size: 1.5rem; - } - - .create-auction-btn { - padding: 0.75rem 1.5rem; - font-size: 0.95rem; - } - - .recently-viewed-title { - font-size: 1.25rem; - } + .listings-grid { + padding: var(--space-2); + gap: var(--space-4); + } + + .empty-state { + padding: var(--space-8) var(--space-4); + } + + .filter-card { + padding: var(--space-3); + } + + .page-header { + padding: var(--space-5); + } + + .display-4 { + font-size: var(--text-xl); + } + + .create-auction-btn { + padding: var(--space-3) var(--space-6); + font-size: var(--text-sm); + } + + .recently-viewed-title { + font-size: var(--text-lg); + } } diff --git a/auctions/static/css/pages/login.css b/auctions/static/css/pages/login.css index d1f0c24..c73c422 100644 --- a/auctions/static/css/pages/login.css +++ b/auctions/static/css/pages/login.css @@ -1,249 +1,167 @@ -/* Login Page Styles */ +/* ================================= + LOGIN PAGE STYLES + ================================= */ + +/* Main card for the login form */ .card { - background: var(--bg-main); - border-radius: 15px; - box-shadow: var(--shadow-lg); - transition: transform 0.3s ease; - width: 100%; + background: var(--bg-elevated); + border-radius: var(--radius-2xl); + box-shadow: var(--shadow-lg); + transition: transform 0.3s ease; + width: 100%; + border: none; } .card:hover { - transform: translateY(-5px); + transform: translateY(-5px); } -/* Header Section */ +/* Header section */ .card-title { - color: var(--text-primary); - font-weight: 600; - font-size: 2rem; - margin-bottom: 0.5rem; + color: var(--text-primary); + font-weight: var(--font-semibold); + font-size: var(--text-3xl); + margin-bottom: var(--space-2); } -/* Icon Styles */ +/* User icon */ .fa-user-circle { - color: #3498db; - transition: transform 0.3s ease; + color: var(--primary-500); + transition: transform 0.3s ease; } .card:hover .fa-user-circle { - transform: scale(1.1); + transform: scale(1.1); } -/* Form Styles */ +/* Form styles */ .form-label { - font-size: 0.95rem; - font-weight: 500; - color: var(--text-primary); - margin-bottom: 0.75rem; - display: flex; - align-items: center; + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--text-primary); + margin-bottom: var(--space-3); + display: flex; + align-items: center; } .input-group { - transition: all 0.3s ease; - border-radius: 10px; - overflow: hidden; - background: var(--bg-main); - border: 1px solid var(--border-light); + transition: var(--transition-all); + border-radius: var(--radius-lg); + overflow: hidden; + background: var(--bg-primary); + border: 1px solid var(--border-light); } .input-group:focus-within { - box-shadow: 0 0 0 0.25rem rgba(52, 152, 219, 0.25); + box-shadow: 0 0 0 0.25rem rgba(from var(--primary-500) r g b / 0.25); } .input-group-text { - background: var(--bg-main); - border: 2px solid var(--border-light); - border-right: none; - color: var(--text-primary); - padding: 0.75rem 1rem; + background: var(--bg-primary); + border: 2px solid var(--border-light); + border-right: none; + color: var(--text-primary); + padding: var(--space-3) var(--space-4); } .form-control { - border: 2px solid var(--border-light); - border-left: none; - padding: 1rem; - font-size: 1rem; - color: var(--text-primary); - background: var(--bg-secondary); + border: 2px solid var(--border-light); + border-left: none; + padding: var(--space-4); + font-size: var(--text-base); + color: var(--text-primary); + background: var(--bg-secondary); } .form-control:focus { - box-shadow: none; - border: none; - border-left: 1px solids rgba(52, 152, 219, 0.25); + box-shadow: none; + border: none; + border-left: 1px solid rgba(from var(--primary-500) r g b / 0.25); } .form-control::placeholder { - color: var(--text-secondary); - font-size: 0.9rem; + color: var(--text-secondary); + font-size: var(--text-sm); } -/* Button Styles */ +/* Button styles */ .btn-primary { - background: linear-gradient(45deg, #3498db, #2980b9); - border: none; - padding: 1rem 2rem; - font-weight: 500; - font-size: 1.1rem; - transition: all 0.3s ease; - border-radius: 10px; + background: var(--nav-gradient); + border: none; + padding: var(--space-4) var(--space-8); + font-weight: var(--font-medium); + font-size: 1.1rem; + transition: var(--transition-all); + border-radius: var(--radius-lg); } .btn-primary:hover { - transform: translateY(-2px); - box-shadow: 0 5px 15px rgba(52, 152, 219, 0.3); + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(from var(--primary-500) r g b / 0.3); } .btn-primary:active { - transform: translateY(0); + transform: translateY(0); } -/* Alert Styling */ +/* Alert styling for errors */ .alert { - border: none; - border-radius: 10px; - background-color: #f8d7da; - border-left: 4px solid #dc3545; + border: none; + border-radius: var(--radius-lg); + background-color: rgba(from var(--error) r g b / 0.1); + border-left: 4px solid var(--error); } .alert i { - color: #dc3545; + color: var(--error); } .text-center > p { - color: var(--text-primary); + color: var(--text-primary); } -/* Register Link */ +/* Register link */ .text-primary { - color: #3498db !important; - transition: color 0.3s ease; + color: var(--primary-500) !important; + transition: color 0.3s ease; } .text-primary:hover { - color: #2980b9 !important; - text-decoration: none; + color: var(--primary-600) !important; + text-decoration: none; } -/* Tablet Styles (≥768px and <992px) */ -@media (min-width: 768px) and (max-width: 991.98px) { - .col-md-6 { - width: 70%; - } - - .card-body { - padding: 2rem !important; - } - - .card-title { - font-size: 1.75rem; - } - - .fa-user-circle { - font-size: 3.5em; - } +/* Responsive adjustments */ +@media (max-width: 991.98px) { + .card-body { + padding: var(--space-8) !important; + } - .form-control-lg { - font-size: 1rem; - } + .card-title { + font-size: var(--text-2xl); + } } -/* Mobile Styles (<768px) */ @media (max-width: 767.98px) { - .container { - padding: 1rem; - } - - .col-md-6 { - width: 100%; - padding: 0 0.5rem; - } - - .card { - margin: 0.5rem; - } - - .card-body { - padding: 1.5rem !important; - } + .card-body { + padding: var(--space-6) !important; + } - .card-title { - font-size: 1.5rem; - } - - .fa-user-circle { - font-size: 3em; - } - - .form-label { - font-size: 0.9rem; - } - - .input-group { - margin-bottom: 1rem; - } - - .form-control-lg { - font-size: 0.95rem; - padding: 0.75rem; - } - - .btn-lg { - padding: 0.75rem 1rem; - font-size: 1rem; - } + .card-title { + font-size: var(--text-xl); + } } -/* Small Mobile Styles (<576px) */ @media (max-width: 575.98px) { - .container { - padding: 0.5rem; - } - - .card-body { - padding: 1rem !important; - } - - .card-title { - font-size: 1.25rem; - } - - .text-muted { - font-size: 0.85rem; - } - - .form-control-lg { - font-size: 0.9rem; - padding: 0.5rem; - } - - .input-group-text { - padding: 0.5rem; - } - - .btn-lg { - padding: 0.5rem; - font-size: 0.9rem; - } -} - -/* Landscape Mode Adjustments */ -@media (max-height: 500px) and (orientation: landscape) { - .container { - padding: 0.5rem; - } - - .card-body { - padding: 1rem !important; - } - - .fa-user-circle { - font-size: 2.5em; - } - - .card-title { - font-size: 1.25rem; - margin-bottom: 0.25rem; - } + .card-body { + padding: var(--space-4) !important; + } + + .card-title { + font-size: var(--text-lg); + } + + .text-muted { + font-size: var(--text-sm); + } } diff --git a/auctions/static/css/pages/new_auction.css b/auctions/static/css/pages/new_auction.css index 88fe2c7..5c4cbe8 100644 --- a/auctions/static/css/pages/new_auction.css +++ b/auctions/static/css/pages/new_auction.css @@ -1,205 +1,209 @@ -/* Form Container Styles */ +/* ================================= + NEW AUCTION PAGE STYLES + ================================= */ + +/* Form container card */ .card { - background: var(--bg-main); - border: none; - border-radius: 15px; - box-shadow: var(--shadow-lg); - transition: all 0.3s ease; + background: var(--bg-elevated); + border: none; + border-radius: var(--radius-2xl); + box-shadow: var(--shadow-lg); + transition: var(--transition-all); } .card:hover { - transform: translateY(-5px); + transform: translateY(-5px); } -/* Form Header */ +/* Form header */ .card-title { - color: var(--text-primary); - font-size: 2rem; - font-weight: 700; - letter-spacing: -0.5px; + color: var(--text-primary); + font-size: var(--text-3xl); + font-weight: var(--font-bold); + letter-spacing: -0.5px; } .card-title i { - color: var(--primary-color); + color: var(--primary-500); } -/* Form Groups */ +/* Form groups */ .form-group { - margin-bottom: 1.5rem; + margin-bottom: var(--space-6); } .form-label { - color: var(--text-primary); - font-size: 1rem; - font-weight: 600; - display: flex; - align-items: center; - gap: 0.5rem; - margin-bottom: 0.75rem; + color: var(--text-primary); + font-size: var(--text-base); + font-weight: var(--font-semibold); + display: flex; + align-items: center; + gap: var(--space-2); + margin-bottom: var(--space-3); } .form-label i { - color: var(--primary-color); - font-size: 1.1rem; + color: var(--primary-500); + font-size: 1.1rem; } -/* Input Styles */ +/* Input and select styles */ .form-control, .form-select { - background: var(--bg-secondary-form); - border: 2px solid var(--border-color); - color: var(--text-primary); - border-radius: 10px; - padding: 1rem; - font-size: 1rem; - transition: all 0.3s ease; + background: var(--bg-secondary); + border: 2px solid var(--border-medium); + color: var(--text-primary); + border-radius: var(--radius-lg); + padding: var(--space-4); + font-size: var(--text-base); + transition: var(--transition-all); } .form-control:focus, .form-select:focus { - background: var(--bg-main); - border-color: var(--primary-color); - box-shadow: 0 0 0 0.25rem rgba(52, 152, 219, 0.15); - color: var(--text-primary); + background: var(--bg-primary); + border-color: var(--primary-500); + box-shadow: 0 0 0 0.25rem rgba(from var(--primary-500) r g b / 0.15); + color: var(--text-primary); } .form-control::placeholder { - color: var(--text-muted); + color: var(--text-muted); } -/* Textarea Specific */ +/* Textarea specific styles */ textarea.form-control { - min-height: 120px; - resize: none; - line-height: 1.5; + min-height: 120px; + resize: none; + line-height: var(--leading-normal); } -/* Input Groups */ +/* Input group styles */ .input-group { - background: var(--bg-secondary-form); - border-radius: 10px; - overflow: hidden; + background: var(--bg-secondary); + border-radius: var(--radius-lg); + overflow: hidden; } .input-group-text { - background: var(--bg-secondary-form); - border: 2px solid var(--border-color); - border-right: none; - color: var(--text-primary); - padding: 0.75rem 1rem; + background: var(--bg-secondary); + border: 2px solid var(--border-medium); + border-right: none; + color: var(--text-primary); + padding: var(--space-3) var(--space-4); } .input-group .form-control { - border-left: none; + border-left: none; } -/* Form Select */ +/* Form select arrow */ .form-select { - background: var(--bg-secondary-form); - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"); - background-repeat: no-repeat; - background-position: right 0.75rem center; - background-size: 16px 12px; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right var(--space-3) center; + background-size: 16px 12px; } -/* Help Text */ +/* Help text */ .form-text { - color: var(--text-muted); - font-size: 0.875rem; - margin-top: 0.5rem; + color: var(--text-muted); + font-size: var(--text-sm); + margin-top: var(--space-2); } -/* Error States */ +/* Error feedback styles */ .invalid-feedback { - display: flex !important; - align-items: center; - gap: 0.5rem; - color: var(--danger-color); - background: rgba(231, 76, 60, 0.1); - padding: 0.5rem 1rem; - border-radius: 8px; - margin-top: 0.5rem; - font-size: 0.875rem; + display: flex !important; + align-items: center; + gap: var(--space-2); + color: var(--error); + background: rgba(from var(--error) r g b / 0.1); + padding: var(--space-2) var(--space-4); + border-radius: var(--radius-lg); + margin-top: var(--space-2); + font-size: var(--text-sm); } .invalid-feedback i { - color: var(--danger-color); + color: var(--error); } -/* Buttons */ +/* Button styles */ .btn-primary { - background: var(--gradient-primary); - border: none; - padding: 1rem 2rem; - font-weight: 500; - border-radius: 10px; - transition: all 0.3s ease; - color: var(--text-light); + background: var(--nav-gradient); + border: none; + padding: var(--space-4) var(--space-8); + font-weight: var(--font-medium); + border-radius: var(--radius-lg); + transition: var(--transition-all); + color: var(--white); } .btn-primary:hover { - transform: translateY(-2px); - box-shadow: var(--shadow-md); + transform: translateY(-2px); + box-shadow: var(--shadow-md); } .btn-light { - background: var(--bg-secondary); - border: 2px solid var(--border-color); - color: var(--text-primary); - transition: all 0.3s ease; + background: var(--bg-secondary); + border: 2px solid var(--border-medium); + color: var(--text-primary); + transition: var(--transition-all); } .btn-light:hover { - background: var(--bg-accent); - transform: translateY(-2px); + background: var(--primary-100); + border-color: var(--primary-200); + transform: translateY(-2px); } -/* Category Select Styles */ +/* Category select specific styles */ select.form-select { - background-color: var(--bg-secondary-form); - border: 2px solid var(--border-color); - color: var(--text-primary); - padding: 0.8rem 1rem; - border-radius: 10px; - cursor: pointer; - transition: all 0.3s ease; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='%236c757d' stroke='%236c757d' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"); - background-repeat: no-repeat; - background-position: right 1rem center; - background-size: 16px 12px; + background-color: var(--bg-secondary); + border: 2px solid var(--border-medium); + color: var(--text-primary); + padding: var(--space-3) var(--space-4); + border-radius: var(--radius-lg); + cursor: pointer; + transition: var(--transition-all); + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='%236c757d' stroke='%236c757d' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right var(--space-4) center; + background-size: 16px 12px; } select.form-select:focus { - background-color: var(--bg-secondary-form); - border-color: var(--primary-color); - box-shadow: 0 0 0 0.25rem rgba(52, 152, 219, 0.15); + background-color: var(--bg-secondary); + border-color: var(--primary-500); + box-shadow: 0 0 0 0.25rem rgba(from var(--primary-500) r g b / 0.15); } -/* Responsive Adjustments */ +/* Responsive adjustments */ @media (max-width: 768px) { - .card-body { - padding: 1.5rem !important; - } - - .card-title { - font-size: 1.75rem; - } - - .form-control, - .form-select { - font-size: 0.95rem; - padding: 0.75rem; - } - - .btn { - width: 100%; - margin-bottom: 0.5rem; - } - - .form-group { - margin-bottom: 1rem; - } + .card-body { + padding: var(--space-6) !important; + } + + .card-title { + font-size: var(--text-2xl); + } + + .form-control, + .form-select { + font-size: var(--text-base); + padding: var(--space-3); + } + + .btn { + width: 100%; + margin-bottom: var(--space-2); + } + + .form-group { + margin-bottom: var(--space-4); + } } diff --git a/auctions/static/css/pages/register.css b/auctions/static/css/pages/register.css index eb67774..d1f8912 100644 --- a/auctions/static/css/pages/register.css +++ b/auctions/static/css/pages/register.css @@ -1,175 +1,176 @@ -/* Register Page Styles */ +/* ================================= + REGISTER PAGE STYLES + ================================= */ + +/* Main card for the registration form */ .card { - background: var(--bg-main); - border-radius: 15px; - box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1); - transition: transform 0.3s ease; + background: var(--bg-elevated); + border-radius: var(--radius-2xl); + box-shadow: var(--shadow-lg); + transition: transform 0.3s ease; + border: none; + animation: fadeIn 0.5s ease-out; } .card:hover { - transform: translateY(-5px); + transform: translateY(-5px); } -/* Header Section */ +/* Header section */ .card-title { - color: var(--text-primary); - font-size: 2rem; - font-weight: 600; - margin-bottom: 0.5rem; + color: var(--text-primary); + font-size: var(--text-3xl); + font-weight: var(--font-semibold); + margin-bottom: var(--space-2); } -/* Icon Styles */ +/* User icon */ .fa-user-plus { - color: #3498db; - transition: transform 0.3s ease; + color: var(--primary-500); + transition: transform 0.3s ease; } .card:hover .fa-user-plus { - transform: scale(1.1); + transform: scale(1.1); } -/* Form Groups */ +/* Form groups */ .form-group { - margin-bottom: 1.5rem; + margin-bottom: var(--space-6); } .form-label { - font-size: 0.95rem; - font-weight: 500; - color: var(--text-primary); - margin-bottom: 0.75rem; - display: flex; - align-items: center; + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--text-primary); + margin-bottom: var(--space-3); + display: flex; + align-items: center; } .form-label i { - margin-right: 0.5rem; - color: var(--primary-dark); + margin-right: var(--space-2); + color: var(--primary-700); } -/* Input Groups */ +/* Input groups */ .input-group { - transition: all 0.3s ease; - border-radius: 10px; - overflow: hidden; - box-shadow: var(--shadow-sm); + transition: var(--transition-all); + border-radius: var(--radius-lg); + overflow: hidden; + box-shadow: var(--shadow-sm); } .input-group:focus-within { - box-shadow: 0 4px 12px rgba(52, 152, 219, 0.25); + box-shadow: 0 4px 12px rgba(from var(--primary-500) r g b / 0.25); } .input-group-text { - background-color: var(--bg-main); - border: 2px solid var(--bg-main); - border-right: none; - color: var(--text-primary); - padding: 0.75rem 1rem; + background-color: var(--bg-primary); + border: 2px solid var(--bg-primary); + border-right: none; + color: var(--text-primary); + padding: var(--space-3) var(--space-4); } .form-control { - border: 2px solid #e9ecef; - border-left: none; - padding: 1rem; - font-size: 1rem; - color: #2c3e50; + border: 2px solid var(--gray-200); + border-left: none; + padding: var(--space-4); + font-size: var(--text-base); + color: var(--gray-800); } .form-control:focus { - border-color: #3498db; - box-shadow: none; - background-color: #fff; + border-color: var(--primary-500); + box-shadow: none; + background-color: var(--white); } .form-control::placeholder { - color: #95a5a6; - font-size: 0.9rem; + color: var(--gray-500); + font-size: var(--text-sm); } -/* Alert Styling */ +/* Alert styling */ .alert { - border: none; - border-radius: 10px; - background-color: #f8d7da; - border-left: 4px solid #dc3545; + border: none; + border-radius: var(--radius-lg); + background-color: rgba(from var(--error) r g b / 0.1); + border-left: 4px solid var(--error); } .alert i { - color: #dc3545; + color: var(--error); } -/* Button Styles */ +/* Button styles */ .btn-primary { - background: linear-gradient(45deg, #3498db, #2980b9); - border: none; - padding: 1rem 2rem; - font-weight: 500; - font-size: 1.1rem; - transition: all 0.3s ease; - border-radius: 10px; + background: var(--nav-gradient); + border: none; + padding: var(--space-4) var(--space-8); + font-weight: var(--font-medium); + font-size: 1.1rem; + transition: var(--transition-all); + border-radius: var(--radius-lg); } .btn-primary:hover { - transform: translateY(-2px); - box-shadow: 0 5px 15px rgba(52, 152, 219, 0.3); + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(from var(--primary-500) r g b / 0.3); } .btn-primary:active { - transform: translateY(0); + transform: translateY(0); } -/* Login Link */ +/* Login link */ .text-primary { - color: #3498db !important; - transition: color 0.3s ease; + color: var(--primary-500) !important; + transition: color 0.3s ease; } .text-primary:hover { - color: #2980b9 !important; - text-decoration: none; + color: var(--primary-600) !important; + text-decoration: none; } .text-center { - color: var(--text-primary); + color: var(--text-primary); } /* Animation */ @keyframes fadeIn { - from { - opacity: 0; - transform: translateY(20px); - } - - to { - opacity: 1; - transform: translateY(0); - } -} - -.card { - animation: fadeIn 0.5s ease-out; + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } } -/* Responsive Adjustments */ +/* Responsive adjustments */ @media (max-width: 576px) { - .card-body { - padding: 2rem !important; - } - - .card-title { - font-size: 1.75rem; - } - - .form-control { - font-size: 0.95rem; - } - - .btn-primary { - padding: 0.75rem 1.5rem; - font-size: 1rem; - } - - .fa-user-plus { - font-size: 3em !important; - } + .card-body { + padding: var(--space-8) !important; + } + + .card-title { + font-size: var(--text-2xl); + } + + .form-control { + font-size: var(--text-base); + } + + .btn-primary { + padding: var(--space-3) var(--space-6); + font-size: var(--text-base); + } + + .fa-user-plus { + font-size: 3em !important; + } } diff --git a/auctions/static/css/variables.css b/auctions/static/css/variables.css new file mode 100644 index 0000000..bb3d484 --- /dev/null +++ b/auctions/static/css/variables.css @@ -0,0 +1,267 @@ +/* ======================================== + CSS VARIABLES & COLOR PALETTE + ======================================== */ + +/* Import Google Fonts for Auction Application */ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Playfair+Display:wght@400;500;600;700&display=swap'); + +/* ======================================== + LIGHT THEME VARIABLES + ======================================== */ +:root, +[data-theme="light"] { + /* Primary Brand Colors */ + --primary-50: #eff6ff; + --primary-100: #dbeafe; + --primary-200: #bfdbfe; + --primary-300: #93c5fd; + --primary-400: #60a5fa; + --primary-500: #3b82f6; + --primary-600: #2563eb; + --primary-700: #1d4ed8; + --primary-800: #1e40af; + --primary-900: #1e3a8a; + --primary-950: #172554; + + /* Secondary Colors */ + --secondary-50: #f8fafc; + --secondary-100: #f1f5f9; + --secondary-200: #e2e8f0; + --secondary-300: #cbd5e1; + --secondary-400: #94a3b8; + --secondary-500: #64748b; + --secondary-600: #475569; + --secondary-700: #334155; + --secondary-800: #1e293b; + --secondary-900: #0f172a; + --secondary-950: #020617; + + /* Accent Colors */ + --accent-gold: #f59e0b; + --accent-gold-light: #fbbf24; + --accent-gold-dark: #d97706; + --accent-red: #ef4444; + --accent-red-light: #f87171; + --accent-red-dark: #dc2626; + --accent-green: #10b981; + --accent-green-light: #34d399; + --accent-green-dark: #059669; + + /* Neutral Colors */ + --white: #ffffff; + --gray-50: #f9fafb; + --gray-100: #f3f4f6; + --gray-200: #e5e7eb; + --gray-300: #d1d5db; + --gray-400: #9ca3af; + --gray-500: #6b7280; + --gray-600: #4b5563; + --gray-700: #374151; + --gray-800: #1f2937; + --gray-900: #111827; + --black: #000000; + + /* Semantic Colors */ + --success: var(--accent-green); + --success-light: var(--accent-green-light); + --success-dark: var(--accent-green-dark); + --warning: var(--accent-gold); + --warning-light: var(--accent-gold-light); + --warning-dark: var(--accent-gold-dark); + --error: var(--accent-red); + --error-light: var(--accent-red-light); + --error-dark: var(--accent-red-dark); + --info: var(--primary-500); + --info-light: var(--primary-400); + --info-dark: var(--primary-600); + + /* Background Colors */ + --bg-primary: var(--white); + --bg-secondary: var(--gray-50); + --bg-tertiary: var(--gray-100); + --bg-elevated: var(--white); + --bg-overlay: rgba(0, 0, 0, 0.5); + + /* Text Colors */ + --text-primary: var(--gray-900); + --text-secondary: var(--gray-700); + --text-tertiary: var(--gray-600); + --text-inverse: var(--white); + --text-muted: var(--gray-500); + --text-disabled: var(--gray-300); + + /* Border Colors */ + --border-light: var(--gray-200); + --border-medium: var(--gray-300); + --border-dark: var(--gray-400); + --border-focus: var(--primary-500); + + /* Shadow Colors */ + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + --shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + + /* Typography */ + --font-primary: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + --font-display: 'Playfair Display', Georgia, serif; + --font-mono: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; + + /* Font Sizes */ + --text-xs: 0.75rem; /* 12px */ + --text-sm: 0.875rem; /* 14px */ + --text-base: 1rem; /* 16px */ + --text-lg: 1.125rem; /* 18px */ + --text-xl: 1.25rem; /* 20px */ + --text-2xl: 1.5rem; /* 24px */ + --text-3xl: 1.875rem; /* 30px */ + --text-4xl: 2.25rem; /* 36px */ + --text-5xl: 3rem; /* 48px */ + --text-6xl: 3.75rem; /* 60px */ + + /* Font Weights */ + --font-light: 300; + --font-normal: 400; + --font-medium: 500; + --font-semibold: 600; + --font-bold: 700; + --font-extrabold: 800; + + /* Line Heights */ + --leading-tight: 1.25; + --leading-snug: 1.375; + --leading-normal: 1.5; + --leading-relaxed: 1.625; + --leading-loose: 2; + + /* Spacing Scale */ + --space-0: 0; + --space-1: 0.25rem; /* 4px */ + --space-2: 0.5rem; /* 8px */ + --space-3: 0.75rem; /* 12px */ + --space-4: 1rem; /* 16px */ + --space-5: 1.25rem; /* 20px */ + --space-6: 1.5rem; /* 24px */ + --space-8: 2rem; /* 32px */ + --space-10: 2.5rem; /* 40px */ + --space-12: 3rem; /* 48px */ + --space-16: 4rem; /* 64px */ + --space-20: 5rem; /* 80px */ + --space-24: 6rem; /* 96px */ + --space-32: 8rem; /* 128px */ + + /* Border Radius */ + --radius-none: 0; + --radius-sm: 0.125rem; /* 2px */ + --radius-md: 0.375rem; /* 6px */ + --radius-lg: 0.5rem; /* 8px */ + --radius-xl: 0.75rem; /* 12px */ + --radius-2xl: 1rem; /* 16px */ + --radius-3xl: 1.5rem; /* 24px */ + --radius-full: 9999px; + + /* Transitions */ + --transition-fast: 150ms ease; + --transition-normal: 250ms ease; + --transition-slow: 350ms ease; + --transition-all: all var(--transition-normal); + + /* Z-Index Scale */ + --z-dropdown: 1000; + --z-sticky: 1020; + --z-fixed: 1030; + --z-modal-backdrop: 1040; + --z-modal: 1050; + --z-popover: 1060; + --z-tooltip: 1070; + --z-toast: 1080; + + /* Breakpoints (for reference) */ + --breakpoint-sm: 640px; + --breakpoint-md: 768px; + --breakpoint-lg: 1024px; + --breakpoint-xl: 1280px; + --breakpoint-2xl: 1536px; +} + +/* ======================================== + DARK THEME VARIABLES + ======================================== */ +[data-theme="dark"] { + /* Primary Brand Colors (adjusted for dark theme) */ + --primary-50: #1e3a8a; + --primary-100: #1e40af; + --primary-200: #1d4ed8; + --primary-300: #2563eb; + --primary-400: #3b82f6; + --primary-500: #60a5fa; + --primary-600: #93c5fd; + --primary-700: #bfdbfe; + --primary-800: #dbeafe; + --primary-900: #eff6ff; + --primary-950: #f8fafc; + + /* Background Colors */ + --bg-primary: var(--gray-900); + --bg-secondary: var(--gray-800); + --bg-tertiary: var(--gray-700); + --bg-elevated: var(--gray-800); + --bg-overlay: rgba(0, 0, 0, 0.7); + + /* Text Colors */ + --text-primary: var(--gray-100); + --text-secondary: var(--gray-200); + --text-tertiary: var(--gray-300); + --text-inverse: var(--gray-900); + --text-muted: var(--gray-400); + --text-disabled: var(--gray-600); + + /* Border Colors */ + --border-light: var(--gray-700); + --border-medium: var(--gray-600); + --border-dark: var(--gray-500); + --border-focus: var(--primary-400); + + /* Shadow Colors (adjusted for dark theme) */ + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.3); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.4), 0 10px 10px -5px rgba(0, 0, 0, 0.3); + --shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.5); +} + +/* ======================================== + AUCTION-SPECIFIC COLORS + ======================================== */ +:root, +[data-theme="light"] { + /* Auction Status Colors */ + --status-active: var(--accent-green); + --status-pending: var(--accent-gold); + --status-closed: var(--gray-500); + --status-cancelled: var(--accent-red); + + /* Bid Colors */ + --bid-current: var(--accent-gold); + --bid-outbid: var(--accent-red); + --bid-winning: var(--accent-green); + + /* Category Colors */ + --category-art: #8b5cf6; + --category-electronics: #06b6d4; + --category-fashion: #ec4899; + --category-home: #f59e0b; + --category-sports: #10b981; + --category-books: #6366f1; + --category-collectibles: #f97316; + --category-vehicles: #6b7280; + + /* Navigation Gradient */ + --nav-gradient: linear-gradient(135deg, var(--primary-600) 0%, var(--primary-800) 100%); + --nav-gradient-dark: linear-gradient(135deg, var(--primary-800) 0%, var(--primary-900) 100%); +} + +[data-theme="dark"] { + --nav-gradient: var(--nav-gradient-dark); +} diff --git a/auctions/static/js/admin.js b/auctions/static/js/admin.js new file mode 100644 index 0000000..24ab3e8 --- /dev/null +++ b/auctions/static/js/admin.js @@ -0,0 +1,27 @@ +// Auto-refresh of metrics every 30 seconds +setInterval(function() { + fetch('{% url "admin_api_metrics" %}') + .then(response => response.json()) + .then(data => { + // Update metrics in real-time + updateMetrics(data); + }) + .catch(error => console.log('Error updating metrics:', error)); +}, 30000); + +function updateMetrics(data) { + // Implement metric updates + console.log('Metrics updated:', data); +} + +// Bootstrap tooltips +var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); +var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { + return new bootstrap.Tooltip(tooltipTriggerEl); +}); + +// Initialize admin panel +document.addEventListener('DOMContentLoaded', function() { + // Initialize any admin-specific functionality + console.log('Admin panel initialized'); +}); diff --git a/auctions/static/js/error-pages.js b/auctions/static/js/error-pages.js new file mode 100644 index 0000000..d633a37 --- /dev/null +++ b/auctions/static/js/error-pages.js @@ -0,0 +1,331 @@ +/** + * Error Pages JavaScript + * Handles animations and interactive elements for error pages + */ + +class ErrorPageManager { + constructor() { + this.init(); + } + + init() { + this.setupFloatingElements(); + this.setupParticleEffect(); + this.setupButtonAnimations(); + this.setupThemeSupport(); + } + + /** + * Setup floating elements animation + */ + setupFloatingElements() { + const floatingElements = document.querySelectorAll('.floating-element'); + if (floatingElements.length === 0) return; + + floatingElements.forEach((element, index) => { + // Set random animation delay for more natural movement + const delay = Math.random() * 2; + element.style.animationDelay = `${delay}s`; + + // Add random rotation to floating elements + const rotation = Math.random() * 360; + element.style.transform = `rotate(${rotation}deg)`; + }); + } + + /** + * Create particle effect in the background + */ + setupParticleEffect() { + const errorContainer = document.querySelector('.error-container'); + if (!errorContainer) return; + + // Create particles every 2 seconds + setInterval(() => { + this.createParticle(errorContainer); + }, 2000); + + // Create initial particles + for (let i = 0; i < 3; i++) { + setTimeout(() => { + this.createParticle(errorContainer); + }, i * 500); + } + } + + /** + * Create a single particle + */ + createParticle(container) { + const particle = document.createElement('div'); + particle.className = 'particle'; + + // Random properties + const size = Math.random() * 4 + 2; // 2-6px + const startX = Math.random() * 100; + const duration = Math.random() * 4 + 6; // 6-10 seconds + const color = this.getRandomParticleColor(); + + // Apply styles + particle.style.cssText = ` + position: absolute; + width: ${size}px; + height: ${size}px; + background: ${color}; + border-radius: 50%; + left: ${startX}%; + top: 100%; + opacity: 0.3; + animation: particleFloat ${duration}s linear infinite; + pointer-events: none; + `; + + container.appendChild(particle); + + // Remove particle after animation + setTimeout(() => { + if (particle.parentNode) { + particle.parentNode.removeChild(particle); + } + }, duration * 1000); + } + + /** + * Get random particle color based on error type + */ + getRandomParticleColor() { + const colors = [ + '#667eea', + '#764ba2', + '#f093fb', + '#f5576c', + '#9b59b6', + '#8e44ad', + '#7d3c98', + '#6c3483', + '#f39c12', + '#e67e22', + '#d35400', + '#c0392b', + '#e74c3c', + '#c0392b', + '#f39c12', + '#e67e22', + ]; + return colors[Math.floor(Math.random() * colors.length)]; + } + + /** + * Setup button hover animations + */ + setupButtonAnimations() { + const buttons = document.querySelectorAll( + '.btn-custom, .btn-outline-custom' + ); + + buttons.forEach((button) => { + button.addEventListener('mouseenter', () => { + button.style.transform = 'translateY(-2px) scale(1.05)'; + }); + + button.addEventListener('mouseleave', () => { + button.style.transform = 'translateY(0) scale(1)'; + }); + + button.addEventListener('click', (e) => { + // Add ripple effect + this.createRippleEffect(e, button); + }); + }); + } + + /** + * Create ripple effect on button click + */ + createRippleEffect(event, button) { + const ripple = document.createElement('span'); + const rect = button.getBoundingClientRect(); + const size = Math.max(rect.width, rect.height); + const x = event.clientX - rect.left - size / 2; + const y = event.clientY - rect.top - size / 2; + + ripple.style.cssText = ` + position: absolute; + width: ${size}px; + height: ${size}px; + left: ${x}px; + top: ${y}px; + background: rgba(255, 255, 255, 0.3); + border-radius: 50%; + transform: scale(0); + animation: ripple 0.6s linear; + pointer-events: none; + `; + + button.style.position = 'relative'; + button.style.overflow = 'hidden'; + button.appendChild(ripple); + + setTimeout(() => { + if (ripple.parentNode) { + ripple.parentNode.removeChild(ripple); + } + }, 600); + } + + /** + * Setup theme support for error pages + */ + setupThemeSupport() { + // Apply theme class to body if dark theme is active + const savedTheme = localStorage.getItem('theme'); + if (savedTheme === 'dark') { + document.body.setAttribute('data-theme', 'dark'); + } + } + + /** + * Add error-specific animations based on error type + */ + addErrorSpecificAnimations() { + const errorCode = document.querySelector('.error-code'); + if (!errorCode) return; + + const code = errorCode.textContent.trim(); + + switch (code) { + case '404': + this.add404Animations(); + break; + case '400': + this.add400Animations(); + break; + case '403': + this.add403Animations(); + break; + case '500': + this.add500Animations(); + break; + } + } + + /** + * 404 specific animations + */ + add404Animations() { + const icon = document.querySelector('.error-icon i'); + if (icon) { + icon.classList.add('bounce'); + } + } + + /** + * 400 specific animations + */ + add400Animations() { + const icon = document.querySelector('.error-icon i'); + if (icon) { + icon.classList.add('rotate'); + } + } + + /** + * 403 specific animations + */ + add403Animations() { + const icon = document.querySelector('.error-icon i'); + if (icon) { + icon.classList.add('pulse'); + } + } + + /** + * 500 specific animations + */ + add500Animations() { + const icon = document.querySelector('.error-icon i'); + if (icon) { + icon.classList.add('shake'); + } + } +} + +/** + * Utility functions + */ +const ErrorPageUtils = { + /** + * Add CSS animation keyframes dynamically + */ + addAnimationKeyframes() { + const style = document.createElement('style'); + style.textContent = ` + @keyframes particleFloat { + 0% { + transform: translateY(0) rotate(0deg); + opacity: 0.3; + } + 50% { + opacity: 0.6; + } + 100% { + transform: translateY(-100vh) rotate(360deg); + opacity: 0; + } + } + + @keyframes ripple { + to { + transform: scale(4); + opacity: 0; + } + } + `; + document.head.appendChild(style); + }, + + /** + * Smooth scroll to top + */ + scrollToTop() { + window.scrollTo({ + top: 0, + behavior: 'smooth', + }); + }, + + /** + * Go back in history + */ + goBack() { + if (window.history.length > 1) { + window.history.back(); + } else { + window.location.href = '/'; + } + }, +}; + +/** + * Initialize when DOM is loaded + */ +document.addEventListener('DOMContentLoaded', () => { + // Add animation keyframes + ErrorPageUtils.addAnimationKeyframes(); + + // Initialize error page manager + const errorManager = new ErrorPageManager(); + + // Add error-specific animations + errorManager.addErrorSpecificAnimations(); + + // Setup utility functions + window.ErrorPageUtils = ErrorPageUtils; +}); + +/** + * Export for module systems + */ +if (typeof module !== 'undefined' && module.exports) { + module.exports = { ErrorPageManager, ErrorPageUtils }; +} diff --git a/auctions/static/js/layout.js b/auctions/static/js/layout.js new file mode 100644 index 0000000..0fa7c72 --- /dev/null +++ b/auctions/static/js/layout.js @@ -0,0 +1,286 @@ +/** + * Layout JavaScript - Main functionality for the site layout + * Handles theme switching, back to top button, and other layout features + */ + +(function () { + 'use strict'; + + // Configuration + const CONFIG = { + backToTopThreshold: 300, + themeStorageKey: 'theme', + animationDuration: 300, + }; + + // DOM Elements + let themeToggle, themeIcon, backToTopButton, body; + + // Initialize when DOM is loaded + document.addEventListener('DOMContentLoaded', function () { + initializeElements(); + initializeTheme(); + initializeBackToTop(); + initializeAccessibility(); + }); + + /** + * Initialize DOM elements + */ + function initializeElements() { + themeToggle = document.querySelector('.theme-toggle'); + themeIcon = document.getElementById('themeIcon'); + backToTopButton = document.getElementById('back-to-top'); + body = document.body; + } + + /** + * Initialize theme functionality + */ + function initializeTheme() { + if (!themeToggle || !themeIcon) return; + + // Apply saved theme or default to light + const savedTheme = localStorage.getItem(CONFIG.themeStorageKey); + if (savedTheme === 'dark') { + applyDarkTheme(); + } else { + // Default to light theme if no preference is saved + applyLightTheme(); + } + + // Add click event listener + themeToggle.addEventListener('click', toggleTheme); + } + + /** + * Toggle between light and dark theme + */ + function toggleTheme() { + const currentTheme = body.getAttribute('data-theme'); + if (currentTheme === 'dark') { + applyLightTheme(); + } else { + applyDarkTheme(); + } + } + + /** + * Apply light theme + */ + function applyLightTheme() { + body.setAttribute('data-theme', 'light'); + if (themeIcon) { + themeIcon.classList.replace('fa-moon', 'fa-sun'); + } + localStorage.setItem(CONFIG.themeStorageKey, 'light'); + + // Debug log + console.log('Applied light theme'); + + // Dispatch custom event + document.dispatchEvent( + new CustomEvent('themeChanged', { + detail: { theme: 'light' }, + }) + ); + } + + /** + * Apply dark theme + */ + function applyDarkTheme() { + body.setAttribute('data-theme', 'dark'); + if (themeIcon) { + themeIcon.classList.replace('fa-sun', 'fa-moon'); + } + localStorage.setItem(CONFIG.themeStorageKey, 'dark'); + + // Debug log + console.log('Applied dark theme'); + + // Dispatch custom event + document.dispatchEvent( + new CustomEvent('themeChanged', { + detail: { theme: 'dark' }, + }) + ); + } + + // Expose functions globally for HTML onclick handlers + window.toggleTheme = toggleTheme; + + // Debug logging + console.log('toggleTheme function defined:', typeof window.toggleTheme); + + /** + * Initialize back to top button functionality + */ + function initializeBackToTop() { + if (!backToTopButton) return; + + // Initially hide the button + backToTopButton.style.display = 'none'; + + // Show/hide based on scroll position + window.addEventListener('scroll', throttle(handleScroll, 100)); + + // Scroll to top when clicked + backToTopButton.addEventListener('click', scrollToTop); + } + + /** + * Handle scroll event for back to top button + */ + function handleScroll() { + if (window.pageYOffset > CONFIG.backToTopThreshold) { + backToTopButton.style.display = 'flex'; + backToTopButton.style.opacity = '0'; + backToTopButton.style.transform = 'translateY(20px)'; + + // Animate in + requestAnimationFrame(() => { + backToTopButton.style.transition = 'all 0.3s ease'; + backToTopButton.style.opacity = '1'; + backToTopButton.style.transform = 'translateY(0)'; + }); + } else { + backToTopButton.style.opacity = '0'; + backToTopButton.style.transform = 'translateY(20px)'; + + setTimeout(() => { + if (window.pageYOffset <= CONFIG.backToTopThreshold) { + backToTopButton.style.display = 'none'; + } + }, CONFIG.animationDuration); + } + } + + /** + * Scroll to top smoothly + */ + function scrollToTop() { + window.scrollTo({ + top: 0, + behavior: 'smooth', + }); + } + + /** + * Initialize accessibility features + */ + function initializeAccessibility() { + // Skip link functionality + const skipLink = document.querySelector('.skip-link'); + if (skipLink) { + skipLink.addEventListener('click', function (e) { + e.preventDefault(); + const target = document.querySelector(this.getAttribute('href')); + if (target) { + target.focus(); + target.scrollIntoView({ behavior: 'smooth' }); + } + }); + } + + // Keyboard navigation for theme toggle + if (themeToggle) { + themeToggle.addEventListener('keydown', function (e) { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + toggleTheme(); + } + }); + } + + // Keyboard navigation for back to top button + if (backToTopButton) { + backToTopButton.addEventListener('keydown', function (e) { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + scrollToTop(); + } + }); + } + } + + /** + * Throttle function to limit function calls + */ + function throttle(func, limit) { + let inThrottle; + return function () { + const args = arguments; + const context = this; + if (!inThrottle) { + func.apply(context, args); + inThrottle = true; + setTimeout(() => (inThrottle = false), limit); + } + }; + } + + /** + * Debounce function to delay function calls + */ + function debounce(func, wait, immediate) { + let timeout; + return function () { + const context = this; + const args = arguments; + const later = function () { + timeout = null; + if (!immediate) func.apply(context, args); + }; + const callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) func.apply(context, args); + }; + } + + /** + * Show loading state + */ + function showLoading(element) { + if (element) { + element.classList.add('loading'); + element.style.display = 'block'; + } + } + + /** + * Hide loading state + */ + function hideLoading(element) { + if (element) { + element.classList.remove('loading'); + element.style.display = 'none'; + } + } + + /** + * Utility function to add smooth transitions + */ + function addSmoothTransition(element, duration = CONFIG.animationDuration) { + if (element) { + element.style.transition = `all ${duration}ms ease`; + } + } + + // Public API + window.LayoutUtils = { + toggleTheme: toggleTheme, + applyLightTheme: applyLightTheme, + applyDarkTheme: applyDarkTheme, + scrollToTop: scrollToTop, + showLoading: showLoading, + hideLoading: hideLoading, + addSmoothTransition: addSmoothTransition, + throttle: throttle, + debounce: debounce, + }; + + // Console log for debugging + console.log('Layout JavaScript initialized successfully'); +})(); diff --git a/auctions/templates/auctions/admin/analytics.html b/auctions/templates/auctions/admin/analytics.html new file mode 100644 index 0000000..5fbc45d --- /dev/null +++ b/auctions/templates/auctions/admin/analytics.html @@ -0,0 +1,361 @@ +{% extends "auctions/admin/base.html" %} +{% load static %} + +{% block admin_content %} + +
| Categoría | +Total Subastas | +Precio Promedio Inicial | +Precio Promedio Actual | +Total Pujas | +Pujas por Subasta | +
|---|---|---|---|---|---|
| + + {{ category.category|default:"Sin categoría" }} + + | ++ {{ category.count }} + | +${{ category.avg_starting_bid|floatformat:2 }} | +${{ category.avg_current_bid|floatformat:2 }} | +{{ category.total_bids }} | +{{ category.avg_bids_per_listing|floatformat:1 }} | +
| No hay datos de categorías | +|||||
| Usuario | +Total Pujas | +Monto Total Pujado | +Subastas Creadas | +
|---|---|---|---|
|
+
+
+
+ {{ user.username|first|upper }}
+
+ {{ user.username }}
+ |
+ + {{ user.bid_count }} + | +${{ user.total_bid_amount|floatformat:2 }} | +{{ user.listings_created }} | +
| No hay datos de pujadores | +|||
| Usuario | +Actividad Total | +Items en Watchlist | +Nivel de Engagement | +
|---|---|---|---|
| {{ user.username }} | ++ {{ user.total_activity }} + | +{{ user.watchlist_count }} | ++ {% if user.total_activity > 50 %} + Alto + {% elif user.total_activity > 20 %} + Medio + {% else %} + Bajo + {% endif %} + | +
| No hay datos de engagement | +|||
| Título | +Pujas | +Aumento % | +Estado | +
|---|---|---|---|
| + + {{ listing.title|truncatechars:30 }} + + | ++ {{ listing.bid_count }} + | ++ {% if listing.bid_increase %} + +{{ listing.bid_increase|floatformat:1 }}% + {% else %} + - + {% endif %} + | ++ {% if listing.active %} + Activa + {% else %} + Inactiva + {% endif %} + | +
| No hay datos de competencia | +|||
| Título | +Usuario | +Precio | +Estado | +
|---|---|---|---|
| + + {{ listing.title|truncatechars:30 }} + + | +{{ listing.user.username }} | ++ ${{ listing.current_bid|default:listing.starting_bid }} + | ++ {% if listing.active %} + Activa + {% else %} + Inactiva + {% endif %} + | +
| No hay subastas recientes | +|||
| Usuario | +Subasta | +Monto | +Fecha | +
|---|---|---|---|
| {{ bid.user.username }} | ++ + {{ bid.listing.title|truncatechars:25 }} + + | ++ ${{ bid.amount }} + | ++ {{ bid.listing.created|date:"M d, H:i" }} + | +
| No hay pujas recientes | +|||
| Usuario | +Subastas Creadas | +Pujas Realizadas | +Comentarios | +Actividad Total | +
|---|---|---|---|---|
|
+
+
+
+ {{ user.username|first|upper }}
+
+ {{ user.username }}
+ |
+ {{ user.listings_count }} | +{{ user.bids_count }} | +{{ user.comments_count }} | ++ {{ user.total_activity }} + | +
| No hay datos de usuarios | +||||
Actualizando métricas...
+{{ listing.description }}
+ +| ID: | +#{{ listing.id }} | +
| Usuario: | +
+
+
+
+ {{ listing.user.username|first|upper }}
+
+ {{ listing.user.username }}
+ |
+
| Categoría: | ++ {% if listing.category %} + {{ listing.category }} + {% else %} + Sin categoría + {% endif %} + | +
| Fecha Creación: | +{{ listing.created|date:"d/m/Y H:i" }} | +
| Estado: | ++ {% if listing.active %} + Activa + {% else %} + Inactiva + {% endif %} + | +
| Precio Inicial: | +${{ listing.starting_bid }} | +
| Precio Actual: | ++ {% if listing.current_bid %} + ${{ listing.current_bid }} + {% else %} + Sin pujas + {% endif %} + | +
| Incremento: | ++ {% if listing.current_bid and listing.starting_bid %} + + +${{ listing.current_bid|sub:listing.starting_bid|floatformat:2 }} + ({{ listing_stats.price_increase|floatformat:1 }}%) + + {% else %} + - + {% endif %} + | +
| Total Pujas: | +{{ listing_stats.total_bids }} | +
| Pujadores Únicos: | +{{ listing_stats.unique_bidders }} | +
| # | +Usuario | +Monto | +Fecha | +Diferencia | +
|---|---|---|---|---|
| {{ forloop.counter }} | +
+
+
+
+ {{ bid.user.username|first|upper }}
+
+ {{ bid.user.username }}
+ |
+ + ${{ bid.amount }} + | ++ {{ bid.listing.created|date:"d/m/Y H:i" }} + | ++ {% if forloop.first %} + - + {% else %} + {% with prev_bid=forloop.counter0|add:"-1" %} + {% for prev in bids %} + {% if forloop.counter0 == prev_bid %} + + +${{ bid.amount|sub:prev.amount|floatformat:2 }} + + {% endif %} + {% endfor %} + {% endwith %} + {% endif %} + | +
|
+
+ No hay pujas en esta subasta + |
+ ||||
| Usuario | +Comentario | +Fecha | +
|---|---|---|
|
+
+
+
+ {{ comment.user.username|first|upper }}
+
+ {{ comment.user.username }}
+ |
+ {{ comment.text }} | ++ {{ comment.created|date:"d/m/Y H:i" }} + | +
|
+
+ No hay comentarios en esta subasta + |
+ ||
| ID | +Título | +Usuario | +Precio Inicial | +Precio Actual | +Categoría | +Pujas | +Estado | +Fecha | +Acciones | +
|---|---|---|---|---|---|---|---|---|---|
| + #{{ listing.id }} + | +
+
+ {% if listing.image %}
+
+
+
+
+ {% endif %}
+
+
+ {{ listing.title|truncatechars:30 }}
+
+
+ + {{ listing.description|truncatechars:50 }} + |
+
+
+
+
+ {{ listing.user.username|first|upper }}
+
+ {{ listing.user.username }}
+ |
+ + ${{ listing.starting_bid }} + | +
+ {% if listing.current_bid %}
+ ${{ listing.current_bid }}
+ {% if listing.current_bid > listing.starting_bid %}
+ + +{{ listing.current_bid|sub:listing.starting_bid|floatformat:2 }} + + {% endif %} + {% else %} + Sin pujas + {% endif %} + |
+ + {% if listing.category %} + {{ listing.category }} + {% else %} + Sin categoría + {% endif %} + | ++ {{ listing.bids.count }} + | ++ {% if listing.active %} + Activa + {% else %} + Inactiva + {% endif %} + | +
+ {{ listing.created|date:"M d, Y" }}
+ + {{ listing.created|time:"H:i" }} + |
+ + + | +
|
+
+ No se encontraron subastas con los filtros aplicados + |
+ |||||||||
Descarga los datos del sistema en formato CSV para análisis externos.
+| Categoría | +Subastas | +Precio Promedio Inicial | +Precio Promedio Actual | +Total Pujas | +Pujas por Subasta | +Participación % | +
|---|---|---|---|---|---|---|
| + + {{ category.category|default:"Sin categoría" }} + + | ++ {{ category.count }} + | +${{ category.avg_starting_bid|floatformat:2 }} | +${{ category.avg_current_bid|floatformat:2 }} | +{{ category.total_bids }} | +{{ category.avg_bids_per_listing|floatformat:1 }} | +
+ {% widthratio category.count basic_metrics.total_listings 100 as percentage %}
+
+
+
+ |
+
| No hay datos de categorías | +||||||
| # | +Usuario | +Pujas | +Monto Total | +
|---|---|---|---|
| {{ forloop.counter }} | +
+
+
+
+ {{ user.username|first|upper }}
+
+ {{ user.username }}
+ |
+ + {{ user.bid_count }} + | +${{ user.total_bid_amount|floatformat:2 }} | +
| No hay datos de pujadores | +|||
| # | +Usuario | +Actividad | +Watchlist | +
|---|---|---|---|
| {{ forloop.counter }} | +{{ user.username }} | ++ {{ user.total_activity }} + | +{{ user.watchlist_count }} | +
| No hay datos de engagement | +|||
| Usuario | +Nombre | +Subastas | +Pujas | +Comentarios | +Watchlist | +Registro | +Estado | +Acciones | +|
|---|---|---|---|---|---|---|---|---|---|
|
+
+
+
+ {{ user.username|first|upper }}
+
+
+
+ {{ user.username }}
+ {% if user.is_superuser %}
+ Superusuario
+ {% elif user.is_staff %}
+ Staff
+ {% endif %}
+ |
+ + {% if user.email %} + + {{ user.email }} + + {% else %} + Sin email + {% endif %} + | ++ {% if user.first_name or user.last_name %} + {{ user.first_name }} {{ user.last_name }} + {% else %} + - + {% endif %} + | ++ {{ user.listings_count }} + | ++ {{ user.bids_count }} + | ++ {{ user.comments_count }} + | ++ {{ user.watchlist_count }} + | +
+ {{ user.date_joined|date:"M d, Y" }}
+ + {{ user.date_joined|time:"H:i" }} + |
+ + {% if user.is_active %} + Activo + {% else %} + Inactivo + {% endif %} + | +
+
+
+ {% if not user.is_superuser %}
+
+
+
+ {% endif %}
+
+ |
+
|
+
+ No se encontraron usuarios con los filtros aplicados + |
+ |||||||||