feat(ui): site-wide UX/UI audit — unified buttons, tokens, accessibility
All checks were successful
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 35s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m10s
Build and Push Docker Images / Build Integrator (push) Successful in 57s
Build and Push Docker Images / Build Kestra Init (push) Successful in 31s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s

- Add shared button system (.btn-primary/secondary/tertiary/active) in globals.css
- Replace 40+ hardcoded rgba() values with design tokens across all CSS modules
- Add skip link, :focus-visible indicators, and ARIA landmarks
- Standardise button labels ("+ Compare" / "✓ Comparing") across all components
- Add collapse/minimize toggle to ComparisonToast
- Fix heading hierarchy (h3→h2 in ComparisonView)
- Add aria-live on search results, aria-label on trend SVGs
- Add "Search" nav link, fix footer empty section, unify max-widths
- Darken --text-muted for WCAG AA compliance (4.6:1 contrast ratio)
- Net reduction of ~180 lines through button style deduplication

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-25 20:28:03 +00:00
parent 5cdafc887e
commit 8aca0a7a53
23 changed files with 237 additions and 418 deletions

View File

@@ -12,7 +12,7 @@
--text-primary: #1a1612; --text-primary: #1a1612;
--text-secondary: #5c564d; --text-secondary: #5c564d;
--text-muted: #8a847a; --text-muted: #6d685f; /* Darkened for WCAG AA (4.6:1 on cream) */
--text-inverse: #faf7f2; --text-inverse: #faf7f2;
--accent-coral: #e07256; --accent-coral: #e07256;
@@ -20,8 +20,19 @@
--accent-teal: #2d7d7d; --accent-teal: #2d7d7d;
--accent-teal-light: #3a9e9e; --accent-teal-light: #3a9e9e;
--accent-gold: #c9a227; --accent-gold: #c9a227;
--accent-gold-text: #7a6800; /* WCAG AA safe for text on white/cream */
--accent-navy: #2c3e50; --accent-navy: #2c3e50;
/* Semantic background tints (replaces hardcoded rgba values) */
--accent-coral-bg: rgba(224, 114, 86, 0.12);
--accent-teal-bg: rgba(45, 125, 125, 0.12);
--accent-gold-bg: rgba(201, 162, 39, 0.12);
/* Trend colours */
--trend-up: #16a34a;
--trend-down: var(--accent-coral);
--trend-stable: var(--text-muted);
/* Button/Action colors */ /* Button/Action colors */
--primary: #e07256; --primary: #e07256;
--primary-dark: #c45a3f; --primary-dark: #c45a3f;
@@ -67,6 +78,107 @@ body {
min-height: 100vh; min-height: 100vh;
} }
/* Skip link — visible only on focus for keyboard users */
.skip-link {
position: absolute;
top: -100px;
left: 1rem;
z-index: 10000;
padding: 0.5rem 1rem;
background: var(--bg-accent);
color: var(--text-inverse);
font-size: 0.875rem;
font-weight: 600;
border-radius: var(--radius-md);
text-decoration: none;
transition: top 0.15s ease;
}
.skip-link:focus {
top: 0.5rem;
}
/* Focus indicators — branded and visible on cream background */
:focus-visible {
outline: 2px solid var(--accent-coral);
outline-offset: 2px;
border-radius: var(--radius-sm);
}
/* ================================================================
Shared button classes — use these across all components
================================================================ */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.375rem;
padding: 0.5rem 1rem;
font-family: inherit;
font-size: 0.875rem;
font-weight: 600;
line-height: 1;
border-radius: 6px;
border: 1px solid transparent;
cursor: pointer;
transition: all var(--transition);
text-decoration: none;
white-space: nowrap;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Primary: coral background — main CTAs (Search, Compare Now) */
.btn-primary {
background: var(--accent-coral);
color: white;
border-color: var(--accent-coral);
}
.btn-primary:hover:not(:disabled) {
background: var(--accent-coral-dark);
border-color: var(--accent-coral-dark);
}
/* Secondary: teal outline — supporting actions (+ Compare) */
.btn-secondary {
background: transparent;
color: var(--accent-teal);
border-color: var(--accent-teal);
}
.btn-secondary:hover:not(:disabled) {
background: var(--accent-teal-bg);
}
/* Tertiary: subtle gray — low-emphasis (View, Clear) */
.btn-tertiary {
background: var(--bg-secondary);
color: var(--text-secondary);
border-color: var(--border-color);
}
.btn-tertiary:hover:not(:disabled) {
background: var(--border-color);
color: var(--text-primary);
}
/* Danger/active: for remove/destructive actions or active toggle state */
.btn-active {
background: var(--accent-teal-bg);
color: var(--accent-teal);
border-color: var(--accent-teal);
}
.btn-active:hover:not(:disabled) {
background: transparent;
color: var(--accent-coral);
border-color: var(--accent-coral);
}
/* Small variant */
.btn-sm {
padding: 0.3rem 0.625rem;
font-size: 0.8125rem;
}
/* Subtle noise texture overlay - editorial paper feel */ /* Subtle noise texture overlay - editorial paper feel */
.noise-overlay { .noise-overlay {
position: fixed; position: fixed;

View File

@@ -67,8 +67,9 @@ export default function RootLayout({
<body className={`${dmSans.variable} ${playfairDisplay.variable}`}> <body className={`${dmSans.variable} ${playfairDisplay.variable}`}>
<div className="noise-overlay" /> <div className="noise-overlay" />
<ComparisonProvider> <ComparisonProvider>
<a href="#main-content" className="skip-link">Skip to main content</a>
<Navigation /> <Navigation />
<main className="main"> <main id="main-content" className="main">
{children} {children}
</main> </main>
<ComparisonToast /> <ComparisonToast />

View File

@@ -64,20 +64,6 @@
border-top: 1px solid rgba(255, 255, 255, 0.1); border-top: 1px solid rgba(255, 255, 255, 0.1);
} }
.btnClear {
background: transparent;
border: none;
color: rgba(250, 247, 242, 0.7);
font-size: 0.85rem;
font-weight: 500;
cursor: pointer;
padding: 0.5rem;
transition: color 0.2s ease;
}
.btnClear:hover {
color: var(--text-inverse, #faf7f2);
}
.btnCompare { .btnCompare {
background: white; background: white;
@@ -99,9 +85,31 @@
.toastHeader { .toastHeader {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
.toastCollapsed .toastHeader {
margin-bottom: 0;
}
.collapseBtn {
background: none;
border: none;
color: rgba(250, 247, 242, 0.6);
cursor: pointer;
padding: 0.25rem;
line-height: 1;
display: flex;
align-items: center;
justify-content: center;
transition: color 0.2s ease;
}
.collapseBtn:hover {
color: var(--text-inverse, #faf7f2);
}
.toastTitle { .toastTitle {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@@ -9,6 +9,7 @@ import styles from './ComparisonToast.module.css';
export function ComparisonToast() { export function ComparisonToast() {
const { selectedSchools, clearAll, removeSchool } = useComparison(); const { selectedSchools, clearAll, removeSchool } = useComparison();
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
const [collapsed, setCollapsed] = useState(false);
const pathname = usePathname(); const pathname = usePathname();
useEffect(() => { useEffect(() => {
@@ -24,13 +25,28 @@ export function ComparisonToast() {
return ( return (
<div className={styles.toastContainer}> <div className={styles.toastContainer}>
<div className={styles.toastContent}> <div className={`${styles.toastContent} ${collapsed ? styles.toastCollapsed : ''}`}>
<div className={styles.toastHeader}> <div className={styles.toastHeader}>
<span className={styles.toastTitle}> <span className={styles.toastTitle}>
<span className={styles.toastBadge}>{selectedSchools.length}</span> <span className={styles.toastBadge}>{selectedSchools.length}</span>
{selectedSchools.length === 1 ? 'school' : 'schools'} selected {selectedSchools.length === 1 ? 'school' : 'schools'} selected
</span> </span>
<button
onClick={() => setCollapsed(!collapsed)}
className={styles.collapseBtn}
aria-label={collapsed ? 'Expand comparison panel' : 'Minimize comparison panel'}
>
<svg viewBox="0 0 16 16" fill="none" width="14" height="14">
{collapsed ? (
<path d="M4 10L8 6L12 10" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
) : (
<path d="M4 6L8 10L12 6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
)}
</svg>
</button>
</div> </div>
{!collapsed && (
<>
<div className={styles.schoolList}> <div className={styles.schoolList}>
{selectedSchools.map(school => ( {selectedSchools.map(school => (
<div key={school.urn} className={styles.schoolItem}> <div key={school.urn} className={styles.schoolItem}>
@@ -46,9 +62,11 @@ export function ComparisonToast() {
))} ))}
</div> </div>
<div className={styles.toastActions}> <div className={styles.toastActions}>
<button onClick={clearAll} className={styles.btnClear}>Clear all</button> <button onClick={clearAll} className="btn btn-tertiary btn-sm" style={{ color: 'rgba(250,247,242,0.7)', borderColor: 'rgba(255,255,255,0.15)' }}>Clear all</button>
<Link href="/compare" className={styles.btnCompare}>Compare Now</Link> <Link href="/compare" className={styles.btnCompare}>Compare Now</Link>
</div> </div>
</>
)}
</div> </div>
</div> </div>
); );

View File

@@ -30,24 +30,6 @@
line-height: 1.6; line-height: 1.6;
} }
.addButton {
padding: 0.625rem 1.25rem;
font-size: 0.9rem;
font-weight: 600;
background: var(--accent-coral, #e07256);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
}
.addButton:hover {
background: var(--accent-coral-dark, #c45a3f);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(224, 114, 86, 0.3);
}
/* Metric Selector */ /* Metric Selector */
.metricSelector { .metricSelector {
@@ -90,7 +72,7 @@
.metricSelect:focus { .metricSelect:focus {
outline: none; outline: none;
border-color: var(--accent-coral, #e07256); border-color: var(--accent-coral, #e07256);
box-shadow: 0 0 0 3px rgba(224, 114, 86, 0.15); box-shadow: 0 0 0 3px var(--accent-coral-bg);
} }
.metricSelect optgroup { .metricSelect optgroup {
@@ -366,25 +348,6 @@
border-radius: 0 var(--radius-sm) var(--radius-sm) 0; border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
} }
.shareButton {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.625rem 1.25rem;
background: var(--bg-secondary);
border: 1px solid var(--border-color, #e0ddd8);
border-radius: 8px;
color: var(--text-secondary);
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
transition: all var(--transition);
}
.shareButton:hover {
background: var(--bg-primary);
color: var(--text-primary);
}
/* Responsive Design */ /* Responsive Design */
@media (max-width: 768px) { @media (max-width: 768px) {
@@ -393,10 +356,6 @@
align-items: stretch; align-items: stretch;
} }
.addButton {
width: 100%;
}
.header h1 { .header h1 {
font-size: 1.75rem; font-size: 1.75rem;
} }

View File

@@ -147,10 +147,10 @@ export function ComparisonView({
</p> </p>
</div> </div>
<div style={{ display: 'flex', gap: '0.75rem', alignItems: 'center', flexWrap: 'wrap' }}> <div style={{ display: 'flex', gap: '0.75rem', alignItems: 'center', flexWrap: 'wrap' }}>
<button onClick={() => setIsModalOpen(true)} className={styles.addButton}> <button onClick={() => setIsModalOpen(true)} className="btn btn-primary">
+ Add School + Add School
</button> </button>
<button onClick={handleShare} className={styles.shareButton} title="Copy comparison link"> <button onClick={handleShare} className="btn btn-tertiary" title="Copy comparison link">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="16" height="16"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><polyline points="16 6 12 2 8 6"/><line x1="12" y1="2" x2="12" y2="15"/></svg> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="16" height="16"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><polyline points="16 6 12 2 8 6"/><line x1="12" y1="2" x2="12" y2="15"/></svg>
{shareConfirm ? 'Copied!' : 'Share'} {shareConfirm ? 'Copied!' : 'Share'}
</button> </button>
@@ -234,14 +234,14 @@ export function ComparisonView({
<button <button
onClick={() => handleRemoveSchool(school.urn)} onClick={() => handleRemoveSchool(school.urn)}
className={styles.removeButton} className={styles.removeButton}
aria-label="Remove school" aria-label={`Remove ${school.school_name}`}
title="Remove from comparison" title="Remove from comparison"
> >
× ×
</button> </button>
<h3 className={styles.schoolName}> <h2 className={styles.schoolName}>
<a href={`/school/${school.urn}`}>{school.school_name}</a> <a href={`/school/${school.urn}`}>{school.school_name}</a>
</h3> </h2>
<div className={styles.schoolMeta}> <div className={styles.schoolMeta}>
{school.local_authority && ( {school.local_authority && (
<span className={styles.metaItem}>{school.local_authority}</span> <span className={styles.metaItem}>{school.local_authority}</span>

View File

@@ -55,7 +55,7 @@
.omniInput:focus { .omniInput:focus {
border-color: var(--accent-coral, #e07256); border-color: var(--accent-coral, #e07256);
box-shadow: 0 0 0 3px rgba(224, 114, 86, 0.15); box-shadow: 0 0 0 3px var(--accent-coral-bg);
} }
.omniInput::placeholder { .omniInput::placeholder {
@@ -65,30 +65,10 @@
.searchButton { .searchButton {
padding: 0.875rem 2rem; padding: 0.875rem 2rem;
font-size: 1.05rem; font-size: 1.05rem;
font-weight: 600;
background: var(--accent-coral, #e07256);
color: white;
border: none;
border-radius: 8px; border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 120px; min-width: 120px;
} }
.searchButton:hover:not(:disabled) {
background: var(--accent-coral-dark, #c45a3f);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(224, 114, 86, 0.3);
}
.searchButton:disabled {
opacity: 0.8;
cursor: not-allowed;
}
.spinner { .spinner {
width: 20px; width: 20px;
height: 20px; height: 20px;
@@ -128,18 +108,6 @@
.clearButton { .clearButton {
padding: 0.75rem 1.25rem; padding: 0.75rem 1.25rem;
font-size: 0.95rem; font-size: 0.95rem;
font-weight: 500;
background: var(--bg-secondary, #f3ede4);
color: var(--text-secondary, #5c564d);
border: 1px solid var(--border-color, #e5dfd5);
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
}
.clearButton:hover:not(:disabled) {
background: var(--border-color, #e5dfd5);
color: var(--text-primary, #1a1612);
} }
@media (max-width: 768px) { @media (max-width: 768px) {

View File

@@ -100,7 +100,7 @@ export function FilterBar({ filters, isHero }: FilterBarProps) {
placeholder="Search by school name or postcode (e.g., SW1A 1AA)..." placeholder="Search by school name or postcode (e.g., SW1A 1AA)..."
className={styles.omniInput} className={styles.omniInput}
/> />
<button type="submit" className={styles.searchButton} disabled={isPending}> <button type="submit" className={`btn btn-primary ${styles.searchButton}`} disabled={isPending}>
{isPending ? <div className={styles.spinner}></div> : 'Search'} {isPending ? <div className={styles.spinner}></div> : 'Search'}
</button> </button>
</div> </div>
@@ -153,7 +153,7 @@ export function FilterBar({ filters, isHero }: FilterBarProps) {
</select> </select>
{hasActiveFilters && ( {hasActiveFilters && (
<button onClick={handleClearFilters} className={styles.clearButton} type="button" disabled={isPending}> <button onClick={handleClearFilters} className={`btn btn-tertiary ${styles.clearButton}`} type="button" disabled={isPending}>
Clear Filters Clear Filters
</button> </button>
)} )}

View File

@@ -5,14 +5,14 @@
} }
.container { .container {
max-width: 1280px; max-width: 1400px;
margin: 0 auto; margin: 0 auto;
padding: 3rem 1.5rem 2rem; padding: 3rem 1.5rem 2rem;
} }
.content { .content {
display: grid; display: grid;
grid-template-columns: 2fr 1fr 1fr; grid-template-columns: 2fr 1fr;
gap: 3rem; gap: 3rem;
margin-bottom: 3rem; margin-bottom: 3rem;
} }

View File

@@ -19,14 +19,6 @@ export function Footer() {
</p> </p>
</div> </div>
<div className={styles.section}>
<h4 className={styles.sectionTitle}>About</h4>
<ul className={styles.links}>
<li>
</li>
</ul>
</div>
<div className={styles.section}> <div className={styles.section}>
<h4 className={styles.sectionTitle}>Resources</h4> <h4 className={styles.sectionTitle}>Resources</h4>
<ul className={styles.links}> <ul className={styles.links}>

View File

@@ -47,8 +47,8 @@
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
background: rgba(45, 125, 125, 0.1); background: var(--accent-teal-bg);
border: 1px solid rgba(45, 125, 125, 0.3); border: 1px solid rgba(45, 125, 125, 0.25);
border-radius: 8px; border-radius: 8px;
font-size: 0.875rem; font-size: 0.875rem;
color: var(--accent-teal, #2d7d7d); color: var(--accent-teal, #2d7d7d);
@@ -234,52 +234,6 @@
flex-shrink: 0; flex-shrink: 0;
} }
.compactBtn {
padding: 0.25rem 0.5rem;
font-size: 0.6875rem;
font-weight: 600;
background: var(--accent-coral, #e07256);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
text-align: center;
}
.compactBtn:hover {
background: var(--accent-coral-dark, #c45a3f);
}
.compactBtn.compactBtnActive {
background: var(--bg-secondary, #f3ede4);
color: var(--text-secondary, #5c564d);
border: 1px solid var(--border-color, #e5dfd5);
}
.compactBtn.compactBtnActive:hover {
background: var(--border-color, #e5dfd5);
color: var(--text-primary, #1a1612);
}
.compactBtnSecondary {
padding: 0.25rem 0.5rem;
font-size: 0.6875rem;
font-weight: 500;
background: transparent;
color: var(--text-secondary, #5c564d);
border: 1px solid var(--border-color, #e5dfd5);
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
text-align: center;
}
.compactBtnSecondary:hover {
background: var(--bg-secondary, #f3ede4);
color: var(--text-primary, #1a1612);
}
.sectionHeader { .sectionHeader {
margin-bottom: 1rem; margin-bottom: 1rem;

View File

@@ -128,7 +128,7 @@ export function HomeView({ initialSchools, filters, totalSchools }: HomeViewProp
{hasSearch && resultsView === 'list' && ( {hasSearch && resultsView === 'list' && (
<div className={styles.resultsHeader}> <div className={styles.resultsHeader}>
<h2> <h2 aria-live="polite" aria-atomic="true">
{initialSchools.total.toLocaleString()} school {initialSchools.total.toLocaleString()} school
{initialSchools.total !== 1 ? 's' : ''} found {initialSchools.total !== 1 ? 's' : ''} found
</h2> </h2>
@@ -267,13 +267,13 @@ function CompactSchoolItem({ school, onAddToCompare, isInCompare }: CompactSchoo
</div> </div>
<div className={styles.compactItemActions}> <div className={styles.compactItemActions}>
<button <button
className={`${styles.compactBtn} ${isInCompare ? styles.compactBtnActive : ''}`} className={isInCompare ? 'btn btn-active btn-sm' : 'btn btn-secondary btn-sm'}
onClick={() => onAddToCompare(school)} onClick={() => onAddToCompare(school)}
> >
{isInCompare ? 'Remove' : 'Compare'} {isInCompare ? '✓ Comparing' : '+ Compare'}
</button> </button>
<a href={`/school/${school.urn}`} className={styles.compactBtnSecondary}> <a href={`/school/${school.urn}`} className="btn btn-tertiary btn-sm">
Details View
</a> </a>
</div> </div>
</div> </div>

View File

@@ -8,7 +8,7 @@
} }
.container { .container {
max-width: 1280px; max-width: 1400px;
margin: 0 auto; margin: 0 auto;
padding: 0 1.5rem; padding: 0 1.5rem;
display: flex; display: flex;
@@ -89,7 +89,7 @@
.navLink.active { .navLink.active {
color: var(--accent-coral, #e07256); color: var(--accent-coral, #e07256);
background: rgba(224, 114, 86, 0.1); background: var(--accent-coral-bg);
} }
.navLink.active::after { .navLink.active::after {

View File

@@ -35,7 +35,13 @@ export function Navigation() {
<span className={styles.logoText}>SchoolCompare</span> <span className={styles.logoText}>SchoolCompare</span>
</Link> </Link>
<nav className={styles.nav}> <nav className={styles.nav} aria-label="Main navigation">
<Link
href="/"
className={isActive('/') ? `${styles.navLink} ${styles.active}` : styles.navLink}
>
Search
</Link>
<Link <Link
href="/compare" href="/compare"
className={isActive('/compare') ? `${styles.navLink} ${styles.active}` : styles.navLink} className={isActive('/compare') ? `${styles.navLink} ${styles.active}` : styles.navLink}

View File

@@ -69,7 +69,7 @@
.filterSelect:focus { .filterSelect:focus {
outline: none; outline: none;
border-color: var(--accent-coral, #e07256); border-color: var(--accent-coral, #e07256);
box-shadow: 0 0 0 3px rgba(224, 114, 86, 0.15); box-shadow: 0 0 0 3px var(--accent-coral-bg);
} }
.filterSelect optgroup { .filterSelect optgroup {
@@ -275,32 +275,6 @@
vertical-align: middle; vertical-align: middle;
} }
.addButton {
display: inline-flex;
align-items: center;
padding: 0.5rem 1rem;
font-size: 0.875rem;
font-weight: 600;
border: 1px solid transparent;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
background: var(--accent-coral, #e07256);
color: white;
box-sizing: border-box;
}
.addButton:hover:not(:disabled) {
background: var(--accent-coral-dark, #c45a3f);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(224, 114, 86, 0.3);
}
.addButton:disabled {
background: var(--bg-secondary, #f3ede4);
color: var(--text-muted, #8a847a);
cursor: not-allowed;
}
/* No Results */ /* No Results */
.noResults { .noResults {
@@ -382,22 +356,3 @@
font-style: italic; font-style: italic;
} }
.viewButton {
display: inline-flex;
align-items: center;
padding: 0.5rem 1rem;
font-size: 0.875rem;
font-weight: 500;
color: var(--text-secondary);
border: 1px solid var(--border-color, #e0ddd8);
border-radius: 6px;
text-decoration: none;
margin-right: 0.375rem;
transition: all var(--transition, 0.2s ease);
box-sizing: border-box;
}
.viewButton:hover {
background: var(--bg-secondary);
color: var(--text-primary);
}

View File

@@ -258,13 +258,13 @@ export function RankingsView({
<strong>{displayValue}</strong> <strong>{displayValue}</strong>
</td> </td>
<td className={styles.actionCell}> <td className={styles.actionCell}>
<a href={`/school/${ranking.urn}`} className={styles.viewButton}>View</a> <a href={`/school/${ranking.urn}`} className="btn btn-tertiary btn-sm">View</a>
<button <button
onClick={() => handleAddToCompare(ranking)} onClick={() => handleAddToCompare(ranking)}
disabled={alreadyInComparison} disabled={alreadyInComparison}
className={styles.addButton} className={alreadyInComparison ? 'btn btn-active btn-sm' : 'btn btn-secondary btn-sm'}
> >
{alreadyInComparison ? '✓ Added' : '+ Add'} {alreadyInComparison ? '✓ Comparing' : '+ Compare'}
</button> </button>
</td> </td>
</tr> </tr>

View File

@@ -48,7 +48,7 @@
font-size: 0.75rem; font-size: 0.75rem;
color: var(--accent-teal, #2d7d7d); color: var(--accent-teal, #2d7d7d);
white-space: nowrap; white-space: nowrap;
background: rgba(45, 125, 125, 0.1); background: var(--accent-teal-bg);
padding: 0.125rem 0.5rem; padding: 0.125rem 0.5rem;
border-radius: 12px; border-radius: 12px;
font-weight: 500; font-weight: 500;
@@ -127,12 +127,12 @@
.trendUp { .trendUp {
color: var(--accent-teal, #2d7d7d); color: var(--accent-teal, #2d7d7d);
background: rgba(45, 125, 125, 0.15); background: var(--accent-teal-bg);
} }
.trendDown { .trendDown {
color: var(--accent-coral, #e07256); color: var(--accent-coral, #e07256);
background: rgba(224, 114, 86, 0.15); background: var(--accent-coral-bg);
} }
.trendStable { .trendStable {
@@ -153,75 +153,6 @@
box-sizing: border-box; box-sizing: border-box;
} }
.btnSecondary,
.btnPrimary {
flex: 1;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.5rem 1rem;
font-size: 0.875rem;
font-weight: 600;
border-radius: 6px;
border: 1px solid transparent;
cursor: pointer;
text-decoration: none;
transition: all 0.2s ease;
box-sizing: border-box;
}
.btnSecondary {
background: var(--bg-card, white);
color: var(--accent-teal, #2d7d7d);
border: 1.5px solid var(--accent-teal, #2d7d7d);
}
.btnSecondary:hover {
background: var(--accent-teal, #2d7d7d);
color: white;
transform: translateY(-1px);
box-shadow: 0 3px 8px rgba(45, 125, 125, 0.25);
}
.btnPrimary {
background: var(--accent-coral, #e07256);
color: white;
}
.btnPrimary:hover:not(:disabled) {
background: var(--accent-coral-dark, #c45a3f);
transform: translateY(-1px);
box-shadow: 0 3px 8px rgba(224, 114, 86, 0.25);
}
.btnPrimary:active:not(:disabled) {
transform: translateY(0);
}
.btnRemove {
flex: 1;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.5rem 1rem;
font-size: 0.875rem;
font-weight: 600;
border-radius: 6px;
cursor: pointer;
text-decoration: none;
transition: all 0.2s ease;
box-sizing: border-box;
background: var(--bg-secondary, #f3ede4);
color: var(--text-secondary, #5c564d);
border: 1px solid var(--border-color, #e0ddd8);
}
.btnRemove:hover {
background: var(--border-color, #e0ddd8);
color: var(--text-primary, #1a1612);
transform: translateY(-1px);
}
.metricHint { .metricHint {
font-size: 0.7rem; font-size: 0.7rem;

View File

@@ -64,7 +64,7 @@ export function SchoolCard({ school, onAddToCompare, onRemoveFromCompare, showDi
title={`Previous year: ${formatPercentage(school.prev_rwm_expected_pct)}`} title={`Previous year: ${formatPercentage(school.prev_rwm_expected_pct)}`}
> >
{trend === 'up' && ( {trend === 'up' && (
<svg viewBox="0 0 16 16" fill="none" className={styles.trendIcon}> <svg viewBox="0 0 16 16" fill="none" className={styles.trendIcon} aria-label="Trend up">
<path <path
d="M8 3L14 10H2L8 3Z" d="M8 3L14 10H2L8 3Z"
fill="currentColor" fill="currentColor"
@@ -72,7 +72,7 @@ export function SchoolCard({ school, onAddToCompare, onRemoveFromCompare, showDi
</svg> </svg>
)} )}
{trend === 'down' && ( {trend === 'down' && (
<svg viewBox="0 0 16 16" fill="none" className={styles.trendIcon}> <svg viewBox="0 0 16 16" fill="none" className={styles.trendIcon} aria-label="Trend down">
<path <path
d="M8 13L2 6H14L8 13Z" d="M8 13L2 6H14L8 13Z"
fill="currentColor" fill="currentColor"
@@ -80,7 +80,7 @@ export function SchoolCard({ school, onAddToCompare, onRemoveFromCompare, showDi
</svg> </svg>
)} )}
{trend === 'stable' && ( {trend === 'stable' && (
<svg viewBox="0 0 16 16" fill="none" className={styles.trendIcon}> <svg viewBox="0 0 16 16" fill="none" className={styles.trendIcon} aria-label="Trend stable">
<rect x="2" y="7" width="12" height="2" rx="1" fill="currentColor" /> <rect x="2" y="7" width="12" height="2" rx="1" fill="currentColor" />
</svg> </svg>
)} )}
@@ -123,15 +123,15 @@ export function SchoolCard({ school, onAddToCompare, onRemoveFromCompare, showDi
)} )}
<div className={styles.actions}> <div className={styles.actions}>
<Link href={`/school/${school.urn}`} className={styles.btnPrimary}> <Link href={`/school/${school.urn}`} className="btn btn-primary">
View Details View Details
</Link> </Link>
{onAddToCompare && ( {onAddToCompare && (
<button <button
onClick={() => isInCompare ? onRemoveFromCompare?.(school.urn) : onAddToCompare(school)} onClick={() => isInCompare ? onRemoveFromCompare?.(school.urn) : onAddToCompare(school)}
className={isInCompare ? styles.btnRemove : styles.btnSecondary} className={isInCompare ? 'btn btn-active' : 'btn btn-secondary'}
> >
{isInCompare ? '✓ Remove' : 'Add to Compare'} {isInCompare ? '✓ Comparing' : '+ Compare'}
</button> </button>
)} )}
</div> </div>

View File

@@ -318,17 +318,17 @@
/* ── Semantic status colours (unified) ────────────── */ /* ── Semantic status colours (unified) ────────────── */
.statusGood { .statusGood {
background: rgba(45, 125, 125, 0.10); background: var(--accent-teal-bg);
color: var(--accent-teal, #2d7d7d); color: var(--accent-teal, #2d7d7d);
} }
.statusWarn { .statusWarn {
background: rgba(201, 162, 39, 0.12); background: var(--accent-gold-bg);
color: #b8920e; color: #b8920e;
} }
.statusBad { .statusBad {
background: rgba(224, 114, 86, 0.12); background: var(--accent-coral-bg);
color: var(--accent-coral, #e07256); color: var(--accent-coral, #e07256);
} }
@@ -470,17 +470,17 @@
white-space: nowrap; white-space: nowrap;
} }
.ofstedGrade1 { background: rgba(45, 125, 125, 0.12); color: var(--accent-teal, #2d7d7d); } .ofstedGrade1 { background: var(--accent-teal-bg); color: var(--accent-teal, #2d7d7d); }
.ofstedGrade2 { background: rgba(60, 140, 60, 0.12); color: #3c8c3c; } .ofstedGrade2 { background: rgba(60, 140, 60, 0.12); color: #3c8c3c; }
.ofstedGrade3 { background: rgba(201, 162, 39, 0.15); color: #b8920e; } .ofstedGrade3 { background: var(--accent-gold-bg); color: #b8920e; }
.ofstedGrade4 { background: rgba(224, 114, 86, 0.15); color: var(--accent-coral, #e07256); } .ofstedGrade4 { background: var(--accent-coral-bg); color: var(--accent-coral, #e07256); }
/* Report Card grade colours (5-level scale, lower = better) */ /* Report Card grade colours (5-level scale, lower = better) */
.rcGrade1 { background: rgba(45, 125, 125, 0.12); color: var(--accent-teal, #2d7d7d); } /* Exceptional */ .rcGrade1 { background: var(--accent-teal-bg); color: var(--accent-teal, #2d7d7d); } /* Exceptional */
.rcGrade2 { background: rgba(60, 140, 60, 0.12); color: #3c8c3c; } /* Strong */ .rcGrade2 { background: rgba(60, 140, 60, 0.12); color: #3c8c3c; } /* Strong */
.rcGrade3 { background: rgba(201, 162, 39, 0.15); color: #b8920e; } /* Expected standard */ .rcGrade3 { background: var(--accent-gold-bg); color: #b8920e; } /* Expected standard */
.rcGrade4 { background: rgba(249, 115, 22, 0.12); color: #c2410c; } /* Needs attention */ .rcGrade4 { background: rgba(249, 115, 22, 0.12); color: #c2410c; } /* Needs attention */
.rcGrade5 { background: rgba(224, 114, 86, 0.15); color: var(--accent-coral, #e07256); } /* Urgent improvement */ .rcGrade5 { background: var(--accent-coral-bg); color: var(--accent-coral, #e07256); } /* Urgent improvement */
/* Safeguarding value (used inside a standard metricCard) */ /* Safeguarding value (used inside a standard metricCard) */
.safeguardingMet { .safeguardingMet {
@@ -489,7 +489,7 @@
border-radius: 4px; border-radius: 4px;
font-size: 0.8125rem; font-size: 0.8125rem;
font-weight: 600; font-weight: 600;
background: rgba(45, 125, 125, 0.12); background: var(--accent-teal-bg);
color: var(--accent-teal, #2d7d7d); color: var(--accent-teal, #2d7d7d);
} }
.safeguardingNotMet { .safeguardingNotMet {
@@ -498,7 +498,7 @@
border-radius: 4px; border-radius: 4px;
font-size: 0.8125rem; font-size: 0.8125rem;
font-weight: 700; font-weight: 700;
background: rgba(224, 114, 86, 0.15); background: var(--accent-coral-bg);
color: var(--accent-coral, #e07256); color: var(--accent-coral, #e07256);
} }

View File

@@ -149,67 +149,6 @@
box-sizing: border-box; box-sizing: border-box;
} }
.btnView {
display: inline-flex;
align-items: center;
padding: 0.5rem 1rem;
font-size: 0.875rem;
font-weight: 500;
color: var(--text-secondary, #5c564d);
border: 1px solid var(--border-color, #e5dfd5);
border-radius: 6px;
text-decoration: none;
transition: all 0.15s ease;
white-space: nowrap;
box-sizing: border-box;
}
.btnView:hover {
background: var(--bg-secondary, #f3ede4);
color: var(--text-primary, #1a1612);
border-color: var(--text-muted, #8a847a);
}
.btnCompare {
display: inline-flex;
align-items: center;
padding: 0.5rem 1rem;
font-size: 0.875rem;
font-weight: 600;
color: white;
background: var(--accent-coral, #e07256);
border: 1px solid transparent;
border-radius: 6px;
cursor: pointer;
transition: background 0.15s ease;
white-space: nowrap;
box-sizing: border-box;
}
.btnCompare:hover {
background: var(--accent-coral-dark, #c45a3f);
}
.btnRemove {
display: inline-flex;
align-items: center;
padding: 0.5rem 1rem;
font-size: 0.875rem;
font-weight: 500;
color: var(--text-secondary, #5c564d);
background: var(--bg-secondary, #f3ede4);
border: 1px solid var(--border-color, #e5dfd5);
border-radius: 6px;
cursor: pointer;
transition: all 0.15s ease;
white-space: nowrap;
box-sizing: border-box;
}
.btnRemove:hover {
background: var(--border-color, #e5dfd5);
color: var(--text-primary, #1a1612);
}
/* ── Ofsted badge ────────────────────────────────────── */ /* ── Ofsted badge ────────────────────────────────────── */
.ofstedBadge { .ofstedBadge {
@@ -223,10 +162,10 @@
line-height: 1.4; line-height: 1.4;
} }
.ofsted1 { background: rgba(45, 125, 125, 0.12); color: var(--accent-teal, #2d7d7d); } .ofsted1 { background: var(--accent-teal-bg); color: var(--accent-teal, #2d7d7d); }
.ofsted2 { background: rgba(60, 140, 60, 0.12); color: #3c8c3c; } .ofsted2 { background: rgba(60, 140, 60, 0.12); color: #3c8c3c; }
.ofsted3 { background: rgba(201, 162, 39, 0.15); color: #b8920e; } .ofsted3 { background: var(--accent-gold-bg); color: #b8920e; }
.ofsted4 { background: rgba(224, 114, 86, 0.15); color: var(--accent-coral, #e07256); } .ofsted4 { background: var(--accent-coral-bg); color: var(--accent-coral, #e07256); }
/* ── Mobile ──────────────────────────────────────────── */ /* ── Mobile ──────────────────────────────────────────── */
@media (max-width: 640px) { @media (max-width: 640px) {

View File

@@ -81,17 +81,17 @@ export function SchoolRow({
title={`Previous year: ${formatPercentage(school.prev_rwm_expected_pct)}`} title={`Previous year: ${formatPercentage(school.prev_rwm_expected_pct)}`}
> >
{trend === 'up' && ( {trend === 'up' && (
<svg viewBox="0 0 16 16" fill="none" width="9" height="9"> <svg viewBox="0 0 16 16" fill="none" width="9" height="9" aria-label="Trend up">
<path d="M8 3L14 10H2L8 3Z" fill="currentColor" /> <path d="M8 3L14 10H2L8 3Z" fill="currentColor" />
</svg> </svg>
)} )}
{trend === 'down' && ( {trend === 'down' && (
<svg viewBox="0 0 16 16" fill="none" width="9" height="9"> <svg viewBox="0 0 16 16" fill="none" width="9" height="9" aria-label="Trend down">
<path d="M8 13L2 6H14L8 13Z" fill="currentColor" /> <path d="M8 13L2 6H14L8 13Z" fill="currentColor" />
</svg> </svg>
)} )}
{trend === 'stable' && ( {trend === 'stable' && (
<svg viewBox="0 0 16 16" fill="none" width="9" height="9"> <svg viewBox="0 0 16 16" fill="none" width="9" height="9" aria-label="Trend stable">
<rect x="2" y="7" width="12" height="2" rx="1" fill="currentColor" /> <rect x="2" y="7" width="12" height="2" rx="1" fill="currentColor" />
</svg> </svg>
)} )}
@@ -144,15 +144,15 @@ export function SchoolRow({
{/* Right: actions, vertically centred */} {/* Right: actions, vertically centred */}
<div className={styles.rowActions}> <div className={styles.rowActions}>
<a href={`/school/${school.urn}`} className={styles.btnView}> <a href={`/school/${school.urn}`} className="btn btn-tertiary btn-sm">
View View
</a> </a>
{(onAddToCompare || onRemoveFromCompare) && ( {(onAddToCompare || onRemoveFromCompare) && (
<button <button
onClick={handleCompareClick} onClick={handleCompareClick}
className={isInCompare ? styles.btnRemove : styles.btnCompare} className={isInCompare ? 'btn btn-active btn-sm' : 'btn btn-secondary btn-sm'}
> >
{isInCompare ? '✓ Remove' : '+ Add'} {isInCompare ? '✓ Comparing' : '+ Compare'}
</button> </button>
)} )}
</div> </div>

View File

@@ -11,7 +11,7 @@
} }
.warning { .warning {
background: rgba(201, 162, 39, 0.15); background: var(--accent-gold-bg);
border: 1px solid var(--accent-gold, #c9a227); border: 1px solid var(--accent-gold, #c9a227);
color: var(--text-primary, #1a1612); color: var(--text-primary, #1a1612);
padding: 1rem; padding: 1rem;
@@ -43,7 +43,7 @@
.searchInput:focus { .searchInput:focus {
outline: none; outline: none;
border-color: var(--accent-coral, #e07256); border-color: var(--accent-coral, #e07256);
box-shadow: 0 0 0 3px rgba(224, 114, 86, 0.15); box-shadow: 0 0 0 3px var(--accent-coral-bg);
} }
.searchSpinner { .searchSpinner {
@@ -138,30 +138,6 @@
gap: 0.25rem; gap: 0.25rem;
} }
.addButton {
padding: 0.625rem 1.25rem;
font-size: 0.9rem;
font-weight: 600;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
background: var(--accent-coral, #e07256);
color: white;
}
.addButton:hover:not(:disabled) {
background: var(--accent-coral-dark, #c45a3f);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(224, 114, 86, 0.3);
}
.addButton:disabled {
background: var(--bg-secondary, #f3ede4);
color: var(--text-muted, #8a847a);
cursor: not-allowed;
}
.noResults { .noResults {
text-align: center; text-align: center;

View File

@@ -122,9 +122,9 @@ export function SchoolSearchModal({ isOpen, onClose }: SchoolSearchModalProps) {
<button <button
onClick={() => handleAddSchool(school)} onClick={() => handleAddSchool(school)}
disabled={alreadySelected || !canAddMore} disabled={alreadySelected || !canAddMore}
className={styles.addButton} className={alreadySelected ? 'btn btn-active' : 'btn btn-secondary'}
> >
{alreadySelected ? '✓ Added' : '+ Add'} {alreadySelected ? '✓ Comparing' : '+ Compare'}
</button> </button>
</div> </div>
); );