diff --git a/src/App.css b/src/App.css index d1a6a2e..0cd4b4c 100644 --- a/src/App.css +++ b/src/App.css @@ -1987,3 +1987,74 @@ html[dir="rtl"] .chart-export-btn.visible { direction: ltr !important; text-align: left !important; } + +/* ======================================== + Loading Skeleton + ======================================== */ + +@keyframes skeleton-pulse { + 0%, 100% { opacity: 0.4; } + 50% { opacity: 1; } +} + +.skeleton-container { + padding: 80px 24px 24px; + max-width: 1200px; + margin: 0 auto; +} + +.skeleton-stats { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 16px; + margin-bottom: 24px; +} + +.skeleton-card { + background: #f1f5f9; + border-radius: 12px; + padding: 20px; + animation: skeleton-pulse 1.5s ease-in-out infinite; +} + +.skeleton-card-wide { + grid-column: span 2; +} + +.skeleton-line { + background: #e2e8f0; + border-radius: 6px; +} + +.skeleton-line-short { + width: 40%; + height: 14px; + margin-bottom: 12px; +} + +.skeleton-line-tall { + width: 100%; + height: 120px; +} + +.skeleton-card-wide .skeleton-line-tall { + height: 200px; +} + +.skeleton-charts { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 16px; +} + +@media (max-width: 768px) { + .skeleton-stats { + grid-template-columns: repeat(2, 1fr); + } + .skeleton-charts { + grid-template-columns: 1fr; + } + .skeleton-card-wide { + grid-column: span 1; + } +} diff --git a/src/App.tsx b/src/App.tsx index 33f65c0..86f8288 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,10 @@ -import React, { useState, useEffect, useCallback, ReactNode } from 'react'; +import React, { useState, useEffect, useCallback, ReactNode, lazy, Suspense } from 'react'; import { BrowserRouter as Router, Routes, Route, Link, useLocation } from 'react-router-dom'; -import Dashboard from './components/Dashboard'; -import Comparison from './components/Comparison'; -import Slides from './components/Slides'; + +const Dashboard = lazy(() => import('./components/Dashboard')); +const Comparison = lazy(() => import('./components/Comparison')); +const Slides = lazy(() => import('./components/Slides')); +import LoadingSkeleton from './components/shared/LoadingSkeleton'; import { fetchData, getCacheStatus, refreshData } from './services/dataService'; import { useLanguage } from './contexts/LanguageContext'; import type { MuseumRecord, CacheStatus, DataErrorType } from './types'; @@ -83,9 +85,8 @@ function App() { if (loading) { return ( -
{t('app.loading')}
+