diff --git a/nextjs-app/app/globals.css b/nextjs-app/app/globals.css index fded7f2..2c1ffdb 100644 --- a/nextjs-app/app/globals.css +++ b/nextjs-app/app/globals.css @@ -67,7 +67,7 @@ body { min-height: 100vh; } -/* Subtle noise texture overlay */ +/* Subtle noise texture overlay - editorial paper feel */ .noise-overlay { position: fixed; top: 0; @@ -75,7 +75,7 @@ body { width: 100%; height: 100%; pointer-events: none; - opacity: 0.03; + opacity: 0.06; z-index: 1000; background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E"); } diff --git a/nextjs-app/components/EmptyState.module.css b/nextjs-app/components/EmptyState.module.css index c431950..c2cfa38 100644 --- a/nextjs-app/components/EmptyState.module.css +++ b/nextjs-app/components/EmptyState.module.css @@ -5,43 +5,53 @@ justify-content: center; padding: 4rem 2rem; text-align: center; - background: white; - border: 2px dashed #e5e7eb; - border-radius: 12px; + background: var(--bg-card, white); + border: 2px solid var(--border-color, #e5dfd5); + border-radius: 16px; min-height: 400px; + box-shadow: var(--shadow-soft, 0 2px 8px rgba(26, 22, 18, 0.06)); } .icon { - color: #d1d5db; + color: var(--text-muted, #8a847a); margin-bottom: 1.5rem; + opacity: 0.7; } .title { - margin: 0 0 0.5rem 0; - font-size: 1.25rem; - font-weight: 600; - color: #1f2937; + margin: 0 0 0.75rem 0; + font-size: 1.5rem; + font-weight: 700; + color: var(--text-primary, #1a1612); + font-family: var(--font-playfair), 'Playfair Display', serif; } .message { - margin: 0 0 1.5rem 0; + margin: 0 0 2rem 0; font-size: 1rem; - color: #6b7280; + color: var(--text-secondary, #5c564d); max-width: 500px; + line-height: 1.6; } .button { - padding: 0.75rem 1.5rem; + padding: 0.875rem 2rem; font-size: 1rem; - font-weight: 500; - background: #3b82f6; + font-weight: 600; + background: var(--accent-coral, #e07256); color: white; border: none; - border-radius: 6px; + border-radius: 8px; cursor: pointer; transition: all 0.2s ease; } .button:hover { - background: #2563eb; + background: var(--accent-coral-dark, #c45a3f); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(224, 114, 86, 0.3); +} + +.button:active { + transform: translateY(0); } diff --git a/nextjs-app/components/HomeView.module.css b/nextjs-app/components/HomeView.module.css index e6f7b66..202c2f4 100644 --- a/nextjs-app/components/HomeView.module.css +++ b/nextjs-app/components/HomeView.module.css @@ -12,11 +12,12 @@ } .heroTitle { - font-size: 2.75rem; + font-size: 3rem; font-weight: 700; color: var(--text-primary, #1a1612); margin-bottom: 1rem; - line-height: 1.2; + line-height: 1.1; + letter-spacing: -0.02em; font-family: var(--font-playfair), 'Playfair Display', serif; } @@ -58,11 +59,22 @@ .sectionHeader h2 { font-size: 1.875rem; font-weight: 700; - margin-bottom: 0.5rem; + margin-bottom: 0.75rem; color: var(--text-primary, #1a1612); font-family: var(--font-playfair), 'Playfair Display', serif; } +/* Decorative coral bar under section headings */ +.sectionHeader h2::after { + content: ''; + display: block; + width: 60px; + height: 3px; + background: var(--accent-coral, #e07256); + border-radius: 2px; + margin-top: 0.75rem; +} + .sectionDescription { font-size: 1rem; color: var(--text-secondary, #5c564d); @@ -102,6 +114,33 @@ margin-bottom: 2rem; } +/* Staggered grid entry animation */ +.grid > * { + animation: gridItemFadeIn 0.4s ease-out both; +} + +.grid > *:nth-child(1) { animation-delay: 0ms; } +.grid > *:nth-child(2) { animation-delay: 50ms; } +.grid > *:nth-child(3) { animation-delay: 100ms; } +.grid > *:nth-child(4) { animation-delay: 150ms; } +.grid > *:nth-child(5) { animation-delay: 200ms; } +.grid > *:nth-child(6) { animation-delay: 250ms; } +.grid > *:nth-child(7) { animation-delay: 300ms; } +.grid > *:nth-child(8) { animation-delay: 350ms; } +.grid > *:nth-child(9) { animation-delay: 400ms; } +.grid > *:nth-child(n+10) { animation-delay: 450ms; } + +@keyframes gridItemFadeIn { + from { + opacity: 0; + transform: translateY(16px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + .emptyState { text-align: center; padding: 4rem 2rem; diff --git a/nextjs-app/components/LoadingSkeleton.module.css b/nextjs-app/components/LoadingSkeleton.module.css index 4fe32c7..ba0da7f 100644 --- a/nextjs-app/components/LoadingSkeleton.module.css +++ b/nextjs-app/components/LoadingSkeleton.module.css @@ -5,17 +5,23 @@ } .skeletonCard { - background: white; - border: 1px solid #e5e7eb; - border-radius: 8px; + background: var(--bg-card, white); + border: 1px solid var(--border-color, #e5dfd5); + border-radius: 12px; padding: 1.5rem; + box-shadow: var(--shadow-soft, 0 2px 8px rgba(26, 22, 18, 0.06)); } .skeleton { - background: linear-gradient(90deg, #f3f4f6 25%, #e5e7eb 50%, #f3f4f6 75%); + background: linear-gradient( + 90deg, + var(--bg-secondary, #f3ede4) 25%, + rgba(224, 114, 86, 0.08) 50%, + var(--bg-secondary, #f3ede4) 75% + ); background-size: 200% 100%; - animation: shimmer 1.5s infinite; - border-radius: 4px; + animation: shimmer 1.5s ease-in-out infinite; + border-radius: 6px; } @keyframes shimmer { @@ -50,8 +56,8 @@ gap: 1rem; margin-bottom: 1.5rem; padding: 1rem; - background: #f9fafb; - border-radius: 6px; + background: var(--bg-secondary, #f3ede4); + border-radius: 8px; } .metric { @@ -76,10 +82,11 @@ } .skeletonListItem { - background: white; - border: 1px solid #e5e7eb; - border-radius: 8px; + background: var(--bg-card, white); + border: 1px solid var(--border-color, #e5dfd5); + border-radius: 12px; padding: 1.5rem; + box-shadow: var(--shadow-soft, 0 2px 8px rgba(26, 22, 18, 0.06)); } .listTitle { diff --git a/nextjs-app/components/Navigation.module.css b/nextjs-app/components/Navigation.module.css index e5ac220..73bad29 100644 --- a/nextjs-app/components/Navigation.module.css +++ b/nextjs-app/components/Navigation.module.css @@ -63,16 +63,39 @@ transition: all 0.2s ease; } +/* Sliding underline effect */ +.navLink::after { + content: ''; + position: absolute; + bottom: 4px; + left: 1rem; + right: 1rem; + height: 2px; + background: var(--accent-coral, #e07256); + transform: scaleX(0); + transform-origin: left; + transition: transform 0.25s ease; + border-radius: 1px; +} + .navLink:hover { color: var(--text-primary, #1a1612); background: var(--bg-secondary, #f3ede4); } +.navLink:hover::after { + transform: scaleX(1); +} + .navLink.active { color: var(--accent-coral, #e07256); background: rgba(224, 114, 86, 0.1); } +.navLink.active::after { + transform: scaleX(1); +} + .badge { display: inline-flex; align-items: center; @@ -85,6 +108,22 @@ color: white; background: var(--accent-coral, #e07256); border-radius: 9999px; + animation: badgePop 0.3s ease-out; + box-shadow: 0 2px 6px rgba(224, 114, 86, 0.4); +} + +@keyframes badgePop { + 0% { + transform: scale(0.6); + opacity: 0; + } + 50% { + transform: scale(1.2); + } + 100% { + transform: scale(1); + opacity: 1; + } } @media (max-width: 640px) { diff --git a/nextjs-app/components/Pagination.module.css b/nextjs-app/components/Pagination.module.css index 9a5cbc7..919f34b 100644 --- a/nextjs-app/components/Pagination.module.css +++ b/nextjs-app/components/Pagination.module.css @@ -8,7 +8,7 @@ .info { font-size: 0.875rem; - color: #6b7280; + color: var(--text-muted, #8a847a); } .controls { @@ -21,17 +21,18 @@ padding: 0.5rem 1rem; font-size: 0.875rem; font-weight: 500; - background: white; - color: #374151; - border: 1px solid #d1d5db; - border-radius: 6px; + background: var(--bg-card, white); + color: var(--text-secondary, #5c564d); + border: 1px solid var(--border-color, #e5dfd5); + border-radius: 8px; cursor: pointer; transition: all 0.2s ease; } .navButton:hover:not(:disabled) { - background: #f9fafb; - border-color: #9ca3af; + background: var(--bg-secondary, #f3ede4); + border-color: var(--accent-coral, #e07256); + color: var(--text-primary, #1a1612); } .navButton:disabled { @@ -50,26 +51,32 @@ padding: 0.5rem 0.75rem; font-size: 0.875rem; font-weight: 500; - background: white; - border: 1px solid #d1d5db; - border-radius: 6px; + background: var(--bg-card, white); + border: 1px solid var(--border-color, #e5dfd5); + border-radius: 8px; cursor: pointer; transition: all 0.2s ease; } .pageButton { - color: #374151; + color: var(--text-secondary, #5c564d); } .pageButton:hover { - background: #f9fafb; - border-color: #9ca3af; + background: var(--bg-secondary, #f3ede4); + border-color: var(--accent-coral, #e07256); + color: var(--text-primary, #1a1612); } .pageButtonActive { - background: #3b82f6; + background: var(--accent-coral, #e07256); color: white; - border-color: #3b82f6; + border-color: var(--accent-coral, #e07256); +} + +.pageButtonActive:hover { + background: var(--accent-coral-dark, #c45a3f); + border-color: var(--accent-coral-dark, #c45a3f); } .ellipsis { @@ -77,7 +84,7 @@ align-items: center; padding: 0.5rem 0.75rem; font-size: 0.875rem; - color: #9ca3af; + color: var(--text-muted, #8a847a); } @media (max-width: 640px) { diff --git a/nextjs-app/components/RankingsView.module.css b/nextjs-app/components/RankingsView.module.css index 66eb8cf..f3a3ae0 100644 --- a/nextjs-app/components/RankingsView.module.css +++ b/nextjs-app/components/RankingsView.module.css @@ -143,6 +143,11 @@ border-bottom: none; } +/* Alternating row backgrounds for visual rhythm */ +.rankingsTable tbody tr:nth-child(even) { + background: rgba(243, 237, 228, 0.5); +} + .rankingsTable tbody tr:hover { background: var(--bg-secondary, #f3ede4); } @@ -168,14 +173,51 @@ color: var(--text-primary, #1a1612); } -.medal { - font-size: 1.5rem; - line-height: 1; +/* Styled rank badges for top 3 */ +.rankBadge { + display: inline-flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + border-radius: 50%; + font-size: 1rem; + font-weight: 700; + color: white; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + position: relative; +} + +.rankBadge::before { + content: ''; + position: absolute; + inset: -2px; + border-radius: 50%; + border: 2px solid transparent; + background: linear-gradient(135deg, rgba(255, 255, 255, 0.4), transparent) border-box; + mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0); + mask-composite: exclude; + -webkit-mask-composite: xor; +} + +.rankBadge1 { + background: linear-gradient(135deg, #c9a227 0%, #e8c547 50%, #c9a227 100%); + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); +} + +.rankBadge2 { + background: linear-gradient(135deg, #8c8c8c 0%, #c0c0c0 50%, #8c8c8c 100%); + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); +} + +.rankBadge3 { + background: linear-gradient(135deg, #a5673f 0%, #cd7f32 50%, #a5673f 100%); + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); } .rankNumber { font-size: 1rem; - color: var(--accent-gold, #c9a227); + color: var(--text-secondary, #5c564d); } .schoolCell { @@ -280,8 +322,10 @@ padding: 0.75rem 0.5rem; } - .medal { - font-size: 1.25rem; + .rankBadge { + width: 30px; + height: 30px; + font-size: 0.875rem; } .schoolHeader { diff --git a/nextjs-app/components/RankingsView.tsx b/nextjs-app/components/RankingsView.tsx index eac2334..a8f6b94 100644 --- a/nextjs-app/components/RankingsView.tsx +++ b/nextjs-app/components/RankingsView.tsx @@ -188,14 +188,13 @@ export function RankingsView({ className={isTopThree ? styles[`rank${rank}`] : ''} >