= {
1: 'good',
@@ -157,14 +157,15 @@ export function CompareOfsted({
}
>
- Result
+
{schools.map((school, i) => (
|
|
))}
+
- Inspected
+
{schools.map((school, i) => {
const ofsted = data[String(school.urn)]?.ofsted;
const age = yearsSince(ofsted?.inspection_date ?? null);
@@ -176,9 +177,12 @@ export function CompareOfsted({
);
})}
-
- Judgement detail
-
+
+
+
{schools.map((school, i) => {
const ofsted = data[String(school.urn)]?.ofsted;
return (
@@ -196,9 +200,12 @@ export function CompareOfsted({
);
})}
-
- Ofsted page
-
+
+
+
{schools.map((school, i) => {
const url =
data[String(school.urn)]?.ofsted?.ofsted_page_url ??
@@ -211,6 +218,7 @@ export function CompareOfsted({
);
})}
+
);
diff --git a/nextjs-app/components/compare/compareSections.module.css b/nextjs-app/components/compare/compareSections.module.css
index 895adae..5cd0b4b 100644
--- a/nextjs-app/components/compare/compareSections.module.css
+++ b/nextjs-app/components/compare/compareSections.module.css
@@ -31,43 +31,71 @@
margin-top: 1.25rem;
}
+/* Mobile base: each measure is a card; each cell is a school row led by a
+ colour dot + short name. `display: contents` at ≥761px dissolves the card
+ back into the shared grid. */
+.measure {
+ background: var(--bg-card);
+ border: 1px solid var(--border-light);
+ border-radius: 12px;
+ box-shadow: var(--shadow-soft);
+ padding: 0.75rem 0.85rem;
+ margin-bottom: 0.6rem;
+}
+
.rowLabel {
font-size: 0.85rem;
font-weight: 600;
- color: var(--text-secondary);
+ color: var(--text-primary);
display: flex;
align-items: center;
gap: 0.35rem;
- background: var(--bg-secondary);
- border-radius: 6px;
- padding: 0.4rem 0.6rem;
- margin-top: 0.8rem;
+ padding: 0 0 0.1rem;
}
.cell {
- padding: 0.4rem 0.6rem;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+ padding: 0.45rem 0;
+ border-top: 1px solid var(--border-light);
+ margin-top: 0.45rem;
font-size: 0.95rem;
}
-.cell::before {
- content: attr(data-school);
- display: block;
- font-size: 0.72rem;
+.cellTag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.4rem;
+ width: 5rem;
+ flex: none;
+ font-size: 0.8rem;
font-weight: 600;
- color: var(--sc, var(--text-muted));
+ color: var(--sc, var(--text-secondary));
+}
+
+.cellDot {
+ width: 9px;
+ height: 9px;
+ border-radius: 50%;
+ background: var(--dot, var(--text-muted));
+ flex: none;
}
.big {
- font-size: 1.35rem;
+ font-size: 1.05rem;
font-weight: 700;
font-variant-numeric: tabular-nums;
}
.small {
display: block;
+ flex-basis: 100%;
+ padding-left: 5.5rem;
font-size: 0.8rem;
color: var(--text-muted);
- margin-top: 0.1rem;
+ margin-top: -0.05rem;
}
.chip {
@@ -197,20 +225,37 @@
gap: 0 0.75rem;
}
+ /* Dissolve the per-measure card so its label + cells become grid items of
+ .grid, keeping columns aligned across every measure. */
+ .measure {
+ display: contents;
+ }
+
+ .cellTag {
+ display: none;
+ }
+
.rowLabel {
- background: none;
- border-radius: 0;
- margin-top: 0;
+ color: var(--text-secondary);
padding: 0.85rem 0.5rem 0.85rem 0;
border-bottom: 1px solid var(--border-light);
}
.cell {
+ display: block;
padding: 0.85rem 0.25rem;
+ border-top: none;
border-bottom: 1px solid var(--border-light);
+ margin-top: 0;
}
- .cell::before {
- content: none;
+ .big {
+ font-size: 1.35rem;
+ }
+
+ .small {
+ flex-basis: auto;
+ padding-left: 0;
+ margin-top: 0.1rem;
}
}
diff --git a/nextjs-app/components/compare/sectionShared.tsx b/nextjs-app/components/compare/sectionShared.tsx
index 89b5d7a..0c27ea5 100644
--- a/nextjs-app/components/compare/sectionShared.tsx
+++ b/nextjs-app/components/compare/sectionShared.tsx
@@ -10,7 +10,7 @@
import type { CSSProperties, ReactNode } from 'react';
import type { School } from '@/lib/types';
-import { CHART_TEXT_COLORS } from '@/lib/utils';
+import { CHART_COLORS, CHART_TEXT_COLORS, shortName } from '@/lib/utils';
import styles from './compareSections.module.css';
export function Section({
@@ -61,6 +61,29 @@ export function RowLabel({ children, tip }: { children: ReactNode; tip?: string
);
}
+/**
+ * One measure = its row label plus a cell per school. `display: contents` on
+ * desktop (see CSS) makes these flow into the section grid as if this wrapper
+ * weren't here, keeping columns aligned across measures; on mobile the wrapper
+ * becomes a card so each measure reads as its own block.
+ */
+export function Measure({
+ label,
+ tip,
+ children,
+}: {
+ label: ReactNode;
+ tip?: string;
+ children: ReactNode;
+}) {
+ return (
+
+ {label}
+ {children}
+
+ );
+}
+
export function Cell({
school,
index,
@@ -73,9 +96,19 @@ export function Cell({
return (
+ {/* Mobile-only per-school tag (dot + short name); hidden on desktop,
+ where the column header identifies the school. */}
+
+
+ {shortName(school.school_name)}
+
{children}
);
diff --git a/nextjs-app/lib/utils.ts b/nextjs-app/lib/utils.ts
index fa1edf0..1e1305d 100644
--- a/nextjs-app/lib/utils.ts
+++ b/nextjs-app/lib/utils.ts
@@ -59,6 +59,24 @@ export function truncate(text: string, maxLength: number): string {
return text.slice(0, maxLength).trim() + '...';
}
+/**
+ * A compact school label for tight spaces (mobile compare rows, chip bars):
+ * drop the trailing establishment-type words so "Barclay Primary School" →
+ * "Barclay", "St Mary's Catholic Primary School" → "St Mary's". Falls back to
+ * a length-capped truncation for names that don't carry a type suffix.
+ */
+export function shortName(name: string, maxLength = 20): string {
+ let s = name
+ .replace(
+ /\s+(primary|junior|infant|nursery|community|foundation|catholic|academy|school|college)\b.*$/i,
+ '',
+ )
+ .trim();
+ if (!s) s = name;
+ if (s.length > maxLength) s = s.slice(0, maxLength - 1).trim() + '…';
+ return s;
+}
+
/**
* Format a school's age range for display, e.g. "3-11" → "Ages 3–11".
* Display-only — leaves the raw `age_range` field (used for sixth-form