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
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:
@@ -64,20 +64,6 @@
|
||||
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 {
|
||||
background: white;
|
||||
@@ -99,9 +85,31 @@
|
||||
.toastHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
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 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -9,6 +9,7 @@ import styles from './ComparisonToast.module.css';
|
||||
export function ComparisonToast() {
|
||||
const { selectedSchools, clearAll, removeSchool } = useComparison();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const pathname = usePathname();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -24,31 +25,48 @@ export function ComparisonToast() {
|
||||
|
||||
return (
|
||||
<div className={styles.toastContainer}>
|
||||
<div className={styles.toastContent}>
|
||||
<div className={`${styles.toastContent} ${collapsed ? styles.toastCollapsed : ''}`}>
|
||||
<div className={styles.toastHeader}>
|
||||
<span className={styles.toastTitle}>
|
||||
<span className={styles.toastBadge}>{selectedSchools.length}</span>
|
||||
{selectedSchools.length === 1 ? 'school' : 'schools'} selected
|
||||
</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 className={styles.schoolList}>
|
||||
{selectedSchools.map(school => (
|
||||
<div key={school.urn} className={styles.schoolItem}>
|
||||
<span className={styles.schoolName} title={school.school_name}>
|
||||
{school.school_name.length > 28 ? school.school_name.slice(0, 28) + '…' : school.school_name}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => removeSchool(school.urn)}
|
||||
className={styles.removeSchoolBtn}
|
||||
aria-label={`Remove ${school.school_name}`}
|
||||
>×</button>
|
||||
{!collapsed && (
|
||||
<>
|
||||
<div className={styles.schoolList}>
|
||||
{selectedSchools.map(school => (
|
||||
<div key={school.urn} className={styles.schoolItem}>
|
||||
<span className={styles.schoolName} title={school.school_name}>
|
||||
{school.school_name.length > 28 ? school.school_name.slice(0, 28) + '…' : school.school_name}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => removeSchool(school.urn)}
|
||||
className={styles.removeSchoolBtn}
|
||||
aria-label={`Remove ${school.school_name}`}
|
||||
>×</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className={styles.toastActions}>
|
||||
<button onClick={clearAll} className={styles.btnClear}>Clear all</button>
|
||||
<Link href="/compare" className={styles.btnCompare}>Compare Now</Link>
|
||||
</div>
|
||||
<div className={styles.toastActions}>
|
||||
<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>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -30,24 +30,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 */
|
||||
.metricSelector {
|
||||
@@ -90,7 +72,7 @@
|
||||
.metricSelect:focus {
|
||||
outline: none;
|
||||
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 {
|
||||
@@ -366,25 +348,6 @@
|
||||
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 */
|
||||
@media (max-width: 768px) {
|
||||
@@ -393,10 +356,6 @@
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.addButton {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
|
||||
@@ -147,10 +147,10 @@ export function ComparisonView({
|
||||
</p>
|
||||
</div>
|
||||
<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
|
||||
</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>
|
||||
{shareConfirm ? 'Copied!' : 'Share'}
|
||||
</button>
|
||||
@@ -234,14 +234,14 @@ export function ComparisonView({
|
||||
<button
|
||||
onClick={() => handleRemoveSchool(school.urn)}
|
||||
className={styles.removeButton}
|
||||
aria-label="Remove school"
|
||||
aria-label={`Remove ${school.school_name}`}
|
||||
title="Remove from comparison"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<h3 className={styles.schoolName}>
|
||||
<h2 className={styles.schoolName}>
|
||||
<a href={`/school/${school.urn}`}>{school.school_name}</a>
|
||||
</h3>
|
||||
</h2>
|
||||
<div className={styles.schoolMeta}>
|
||||
{school.local_authority && (
|
||||
<span className={styles.metaItem}>{school.local_authority}</span>
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
|
||||
.omniInput:focus {
|
||||
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 {
|
||||
@@ -65,30 +65,10 @@
|
||||
.searchButton {
|
||||
padding: 0.875rem 2rem;
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
background: var(--accent-coral, #e07256);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
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 {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
@@ -128,18 +108,6 @@
|
||||
.clearButton {
|
||||
padding: 0.75rem 1.25rem;
|
||||
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) {
|
||||
|
||||
@@ -100,7 +100,7 @@ export function FilterBar({ filters, isHero }: FilterBarProps) {
|
||||
placeholder="Search by school name or postcode (e.g., SW1A 1AA)..."
|
||||
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'}
|
||||
</button>
|
||||
</div>
|
||||
@@ -153,7 +153,7 @@ export function FilterBar({ filters, isHero }: FilterBarProps) {
|
||||
</select>
|
||||
|
||||
{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
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 3rem 1.5rem 2rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr 1fr;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: 3rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
@@ -19,14 +19,6 @@ export function Footer() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.section}>
|
||||
<h4 className={styles.sectionTitle}>About</h4>
|
||||
<ul className={styles.links}>
|
||||
<li>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className={styles.section}>
|
||||
<h4 className={styles.sectionTitle}>Resources</h4>
|
||||
<ul className={styles.links}>
|
||||
|
||||
@@ -47,8 +47,8 @@
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: rgba(45, 125, 125, 0.1);
|
||||
border: 1px solid rgba(45, 125, 125, 0.3);
|
||||
background: var(--accent-teal-bg);
|
||||
border: 1px solid rgba(45, 125, 125, 0.25);
|
||||
border-radius: 8px;
|
||||
font-size: 0.875rem;
|
||||
color: var(--accent-teal, #2d7d7d);
|
||||
@@ -234,52 +234,6 @@
|
||||
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 {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
@@ -128,7 +128,7 @@ export function HomeView({ initialSchools, filters, totalSchools }: HomeViewProp
|
||||
|
||||
{hasSearch && resultsView === 'list' && (
|
||||
<div className={styles.resultsHeader}>
|
||||
<h2>
|
||||
<h2 aria-live="polite" aria-atomic="true">
|
||||
{initialSchools.total.toLocaleString()} school
|
||||
{initialSchools.total !== 1 ? 's' : ''} found
|
||||
</h2>
|
||||
@@ -267,13 +267,13 @@ function CompactSchoolItem({ school, onAddToCompare, isInCompare }: CompactSchoo
|
||||
</div>
|
||||
<div className={styles.compactItemActions}>
|
||||
<button
|
||||
className={`${styles.compactBtn} ${isInCompare ? styles.compactBtnActive : ''}`}
|
||||
className={isInCompare ? 'btn btn-active btn-sm' : 'btn btn-secondary btn-sm'}
|
||||
onClick={() => onAddToCompare(school)}
|
||||
>
|
||||
{isInCompare ? 'Remove' : 'Compare'}
|
||||
{isInCompare ? '✓ Comparing' : '+ Compare'}
|
||||
</button>
|
||||
<a href={`/school/${school.urn}`} className={styles.compactBtnSecondary}>
|
||||
Details
|
||||
<a href={`/school/${school.urn}`} className="btn btn-tertiary btn-sm">
|
||||
View
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1.5rem;
|
||||
display: flex;
|
||||
@@ -89,7 +89,7 @@
|
||||
|
||||
.navLink.active {
|
||||
color: var(--accent-coral, #e07256);
|
||||
background: rgba(224, 114, 86, 0.1);
|
||||
background: var(--accent-coral-bg);
|
||||
}
|
||||
|
||||
.navLink.active::after {
|
||||
|
||||
@@ -35,7 +35,13 @@ export function Navigation() {
|
||||
<span className={styles.logoText}>SchoolCompare</span>
|
||||
</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
|
||||
href="/compare"
|
||||
className={isActive('/compare') ? `${styles.navLink} ${styles.active}` : styles.navLink}
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
.filterSelect:focus {
|
||||
outline: none;
|
||||
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 {
|
||||
@@ -275,32 +275,6 @@
|
||||
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 */
|
||||
.noResults {
|
||||
@@ -382,22 +356,3 @@
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -258,13 +258,13 @@ export function RankingsView({
|
||||
<strong>{displayValue}</strong>
|
||||
</td>
|
||||
<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
|
||||
onClick={() => handleAddToCompare(ranking)}
|
||||
disabled={alreadyInComparison}
|
||||
className={styles.addButton}
|
||||
className={alreadyInComparison ? 'btn btn-active btn-sm' : 'btn btn-secondary btn-sm'}
|
||||
>
|
||||
{alreadyInComparison ? '✓ Added' : '+ Add'}
|
||||
{alreadyInComparison ? '✓ Comparing' : '+ Compare'}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
font-size: 0.75rem;
|
||||
color: var(--accent-teal, #2d7d7d);
|
||||
white-space: nowrap;
|
||||
background: rgba(45, 125, 125, 0.1);
|
||||
background: var(--accent-teal-bg);
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 12px;
|
||||
font-weight: 500;
|
||||
@@ -127,12 +127,12 @@
|
||||
|
||||
.trendUp {
|
||||
color: var(--accent-teal, #2d7d7d);
|
||||
background: rgba(45, 125, 125, 0.15);
|
||||
background: var(--accent-teal-bg);
|
||||
}
|
||||
|
||||
.trendDown {
|
||||
color: var(--accent-coral, #e07256);
|
||||
background: rgba(224, 114, 86, 0.15);
|
||||
background: var(--accent-coral-bg);
|
||||
}
|
||||
|
||||
.trendStable {
|
||||
@@ -153,75 +153,6 @@
|
||||
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 {
|
||||
font-size: 0.7rem;
|
||||
|
||||
@@ -64,7 +64,7 @@ export function SchoolCard({ school, onAddToCompare, onRemoveFromCompare, showDi
|
||||
title={`Previous year: ${formatPercentage(school.prev_rwm_expected_pct)}`}
|
||||
>
|
||||
{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
|
||||
d="M8 3L14 10H2L8 3Z"
|
||||
fill="currentColor"
|
||||
@@ -72,7 +72,7 @@ export function SchoolCard({ school, onAddToCompare, onRemoveFromCompare, showDi
|
||||
</svg>
|
||||
)}
|
||||
{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
|
||||
d="M8 13L2 6H14L8 13Z"
|
||||
fill="currentColor"
|
||||
@@ -80,7 +80,7 @@ export function SchoolCard({ school, onAddToCompare, onRemoveFromCompare, showDi
|
||||
</svg>
|
||||
)}
|
||||
{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" />
|
||||
</svg>
|
||||
)}
|
||||
@@ -123,15 +123,15 @@ export function SchoolCard({ school, onAddToCompare, onRemoveFromCompare, showDi
|
||||
)}
|
||||
|
||||
<div className={styles.actions}>
|
||||
<Link href={`/school/${school.urn}`} className={styles.btnPrimary}>
|
||||
<Link href={`/school/${school.urn}`} className="btn btn-primary">
|
||||
View Details
|
||||
</Link>
|
||||
{onAddToCompare && (
|
||||
<button
|
||||
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>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -318,17 +318,17 @@
|
||||
|
||||
/* ── Semantic status colours (unified) ────────────── */
|
||||
.statusGood {
|
||||
background: rgba(45, 125, 125, 0.10);
|
||||
background: var(--accent-teal-bg);
|
||||
color: var(--accent-teal, #2d7d7d);
|
||||
}
|
||||
|
||||
.statusWarn {
|
||||
background: rgba(201, 162, 39, 0.12);
|
||||
background: var(--accent-gold-bg);
|
||||
color: #b8920e;
|
||||
}
|
||||
|
||||
.statusBad {
|
||||
background: rgba(224, 114, 86, 0.12);
|
||||
background: var(--accent-coral-bg);
|
||||
color: var(--accent-coral, #e07256);
|
||||
}
|
||||
|
||||
@@ -470,17 +470,17 @@
|
||||
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; }
|
||||
.ofstedGrade3 { background: rgba(201, 162, 39, 0.15); color: #b8920e; }
|
||||
.ofstedGrade4 { background: rgba(224, 114, 86, 0.15); color: var(--accent-coral, #e07256); }
|
||||
.ofstedGrade3 { background: var(--accent-gold-bg); color: #b8920e; }
|
||||
.ofstedGrade4 { background: var(--accent-coral-bg); color: var(--accent-coral, #e07256); }
|
||||
|
||||
/* 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 */
|
||||
.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 */
|
||||
.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) */
|
||||
.safeguardingMet {
|
||||
@@ -489,7 +489,7 @@
|
||||
border-radius: 4px;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
background: rgba(45, 125, 125, 0.12);
|
||||
background: var(--accent-teal-bg);
|
||||
color: var(--accent-teal, #2d7d7d);
|
||||
}
|
||||
.safeguardingNotMet {
|
||||
@@ -498,7 +498,7 @@
|
||||
border-radius: 4px;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 700;
|
||||
background: rgba(224, 114, 86, 0.15);
|
||||
background: var(--accent-coral-bg);
|
||||
color: var(--accent-coral, #e07256);
|
||||
}
|
||||
|
||||
|
||||
@@ -149,67 +149,6 @@
|
||||
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 ────────────────────────────────────── */
|
||||
.ofstedBadge {
|
||||
@@ -223,10 +162,10 @@
|
||||
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; }
|
||||
.ofsted3 { background: rgba(201, 162, 39, 0.15); color: #b8920e; }
|
||||
.ofsted4 { background: rgba(224, 114, 86, 0.15); color: var(--accent-coral, #e07256); }
|
||||
.ofsted3 { background: var(--accent-gold-bg); color: #b8920e; }
|
||||
.ofsted4 { background: var(--accent-coral-bg); color: var(--accent-coral, #e07256); }
|
||||
|
||||
/* ── Mobile ──────────────────────────────────────────── */
|
||||
@media (max-width: 640px) {
|
||||
|
||||
@@ -81,17 +81,17 @@ export function SchoolRow({
|
||||
title={`Previous year: ${formatPercentage(school.prev_rwm_expected_pct)}`}
|
||||
>
|
||||
{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" />
|
||||
</svg>
|
||||
)}
|
||||
{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" />
|
||||
</svg>
|
||||
)}
|
||||
{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" />
|
||||
</svg>
|
||||
)}
|
||||
@@ -144,15 +144,15 @@ export function SchoolRow({
|
||||
|
||||
{/* Right: actions, vertically centred */}
|
||||
<div className={styles.rowActions}>
|
||||
<a href={`/school/${school.urn}`} className={styles.btnView}>
|
||||
<a href={`/school/${school.urn}`} className="btn btn-tertiary btn-sm">
|
||||
View
|
||||
</a>
|
||||
{(onAddToCompare || onRemoveFromCompare) && (
|
||||
<button
|
||||
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>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
}
|
||||
|
||||
.warning {
|
||||
background: rgba(201, 162, 39, 0.15);
|
||||
background: var(--accent-gold-bg);
|
||||
border: 1px solid var(--accent-gold, #c9a227);
|
||||
color: var(--text-primary, #1a1612);
|
||||
padding: 1rem;
|
||||
@@ -43,7 +43,7 @@
|
||||
.searchInput:focus {
|
||||
outline: none;
|
||||
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 {
|
||||
@@ -138,30 +138,6 @@
|
||||
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 {
|
||||
text-align: center;
|
||||
|
||||
@@ -122,9 +122,9 @@ export function SchoolSearchModal({ isOpen, onClose }: SchoolSearchModalProps) {
|
||||
<button
|
||||
onClick={() => handleAddSchool(school)}
|
||||
disabled={alreadySelected || !canAddMore}
|
||||
className={styles.addButton}
|
||||
className={alreadySelected ? 'btn btn-active' : 'btn btn-secondary'}
|
||||
>
|
||||
{alreadySelected ? '✓ Added' : '+ Add'}
|
||||
{alreadySelected ? '✓ Comparing' : '+ Compare'}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user