Add visual polish and micro-interactions for editorial feel
Phase 1 - Critical Fixes: - EmptyState: warm palette, coral button, Playfair Display title - Pagination: design system colors, coral active state - LoadingSkeleton: warm shimmer with coral tint Phase 2 - Signature Patterns: - Navigation: sliding underline hover effect on links - globals.css: increased noise texture opacity for paper feel - RankingsView: alternating row backgrounds - HomeView: decorative coral bar under section headings Phase 3 - Polish: - SchoolCard: SVG trend icons replacing unicode arrows - RankingsView: styled metallic rank badges replacing emoji medals Phase 4 - Micro-interactions: - Navigation badge: pop animation when count changes - HomeView grid: staggered entry animation for cards Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -188,14 +188,13 @@ export function RankingsView({
|
||||
className={isTopThree ? styles[`rank${rank}`] : ''}
|
||||
>
|
||||
<td className={styles.rankCell}>
|
||||
{isTopThree && (
|
||||
<span className={styles.medal}>
|
||||
{rank === 1 && '🥇'}
|
||||
{rank === 2 && '🥈'}
|
||||
{rank === 3 && '🥉'}
|
||||
{isTopThree ? (
|
||||
<span className={`${styles.rankBadge} ${styles[`rankBadge${rank}`]}`}>
|
||||
{rank}
|
||||
</span>
|
||||
) : (
|
||||
<span className={styles.rankNumber}>{rank}</span>
|
||||
)}
|
||||
<span className={styles.rankNumber}>{rank}</span>
|
||||
</td>
|
||||
<td className={styles.schoolCell}>
|
||||
<a href={`/school/${ranking.urn}`} className={styles.schoolLink}>
|
||||
|
||||
@@ -101,9 +101,38 @@
|
||||
}
|
||||
|
||||
.trend {
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
cursor: help;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.trend:hover {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
|
||||
.trendIcon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.trendUp {
|
||||
color: var(--accent-teal, #2d7d7d);
|
||||
background: rgba(45, 125, 125, 0.15);
|
||||
}
|
||||
|
||||
.trendDown {
|
||||
color: var(--accent-coral, #e07256);
|
||||
background: rgba(224, 114, 86, 0.15);
|
||||
}
|
||||
|
||||
.trendStable {
|
||||
color: var(--text-muted, #8a847a);
|
||||
background: rgba(138, 132, 122, 0.15);
|
||||
}
|
||||
|
||||
.actions {
|
||||
|
||||
@@ -55,11 +55,30 @@ export function SchoolCard({ school, onAddToCompare, showDistance, distance }: S
|
||||
<strong>{formatPercentage(school.rwm_expected_pct)}</strong>
|
||||
{school.prev_rwm_expected_pct !== null && (
|
||||
<span
|
||||
className={styles.trend}
|
||||
style={{ color: trendColor }}
|
||||
className={`${styles.trend} ${styles[`trend${trend.charAt(0).toUpperCase() + trend.slice(1)}`]}`}
|
||||
title={`Previous: ${formatPercentage(school.prev_rwm_expected_pct)}`}
|
||||
>
|
||||
{trend === 'up' ? '↑' : trend === 'down' ? '↓' : '→'}
|
||||
{trend === 'up' && (
|
||||
<svg viewBox="0 0 16 16" fill="none" className={styles.trendIcon}>
|
||||
<path
|
||||
d="M8 3L14 10H2L8 3Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
{trend === 'down' && (
|
||||
<svg viewBox="0 0 16 16" fill="none" className={styles.trendIcon}>
|
||||
<path
|
||||
d="M8 13L2 6H14L8 13Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
{trend === 'stable' && (
|
||||
<svg viewBox="0 0 16 16" fill="none" className={styles.trendIcon}>
|
||||
<rect x="2" y="7" width="12" height="2" rx="1" fill="currentColor" />
|
||||
</svg>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user