feat(school-detail): dedupe hero header, drop redundant chip strip
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 49s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s

The hero header rendered the Ofsted signal twice — once as a chip in the
strip and again as a tile in the at-a-glance scorecard — and an
Oversubscribed chip already covered by the First-choice tile's footnote.

Remove the chip strip on both primary and secondary detail views, leaving
the scorecard trio (Results · Ofsted · First-choice) as the single home
for the headline numbers. Widen the scorecard's render gate to hasHeroStats
so a school with an Ofsted rating but no results still shows its Ofsted
signal (previously carried by the chip strip). Drop the now-dead .heroChip
CSS, keeping the shared .tone-* tokens the scorecard's serif number uses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tudor
2026-07-01 12:45:49 +01:00
co-authored by Claude Opus 4.8
parent 207e3c631c
commit b0639db79e
4 changed files with 25 additions and 198 deletions
@@ -1195,51 +1195,8 @@
}
}
/* ── Hero signal chip strip (A2) ─────────────────────────────────────── */
.heroChips {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-top: 1.25rem;
}
.heroChip {
flex: 0 0 240px;
padding: 0.75rem 1rem;
border-radius: 8px;
border-left: 3px solid var(--border-color, #e5dfd5);
background: var(--bg-secondary, #f3ede4);
color: var(--text-primary, #1a1612);
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.heroChipTitle {
font-size: 0.9375rem;
font-weight: 700;
line-height: 1.3;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.heroChipSub {
font-size: 0.75rem;
color: var(--text-secondary, #5c564d);
line-height: 1.4;
}
.heroChipDetail {
font-size: 0.75rem;
font-weight: 600;
line-height: 1.4;
margin-top: 0.1rem;
}
/* Hero tone scheme — independent of the .ofstedGrade{N} / .rcGrade{N} badges
so the same tone class can be applied to a chip (background tint + border)
or a serif number (colour only) without one bleeding into the other. */
/* Hero tone scheme — colour tokens applied to the scorecard's serif Ofsted
number (colour only) without bleeding into the .ofstedGrade{N} badges. */
.tone-teal {
--hero-tone: var(--accent-teal, #2d7d7d);
}
@@ -1256,19 +1213,6 @@
--hero-tone: var(--text-muted, #8a847a);
}
.heroChip.tone-teal,
.heroChip.tone-green,
.heroChip.tone-gold,
.heroChip.tone-coral,
.heroChip.tone-neutral {
border-left-color: var(--hero-tone);
background: color-mix(in srgb, var(--hero-tone) 10%, var(--bg-card, white));
}
.heroChip.tone-neutral {
background: var(--bg-secondary, #f3ede4);
}
/* ── Hero at-a-glance stats (A3) ─────────────────────────────────────── */
.heroStats {
display: flex;
@@ -1340,29 +1284,6 @@
}
@media (max-width: 640px) {
.heroChips {
gap: 0.5rem;
margin-top: 1rem;
}
.heroChip {
min-width: 100%;
}
/* Collapse the "Ofsted pending / No inspection on record" empty state
into a single compact line on phones — it's a non-result, not worth
a full hero card. */
.heroChip[data-ofsted-state="none"] {
padding: 0.5rem 0.75rem;
}
.heroChip[data-ofsted-state="none"] .heroChipSub {
display: none;
}
.heroChip[data-ofsted-state="none"] .heroChipTitle {
font-size: 0.85rem;
color: var(--text-muted, #6d685f);
}
.heroStats {
gap: 1rem 1.5rem;
}
+11 -28
View File
@@ -294,6 +294,13 @@ export function SchoolDetailView({
const heroAcademicYear = latestResults ? formatAcademicYear(latestResults.year) : '';
// Scorecard renders if any tile has content, so a results-less school still
// shows its Ofsted signal (previously carried by the now-removed chip strip).
const hasHeroStats = heroRwm != null
|| heroAtt8 != null
|| ofsted != null
|| admissions?.first_preference_offer_pct != null;
// Label shown in the mobile "section" menu button — the section in view.
const activeNavLabel = (navItems.find((n) => n.id === activeSection) ?? navItems[0])?.label ?? '';
@@ -366,34 +373,10 @@ export function SchoolDetailView({
</div>
</div>
{/* Hero signal chip strip */}
<div className={styles.heroChips}>
<div
className={`${styles.heroChip} ${styles[`tone-${ofstedHeroChip.tone}`]}`}
data-ofsted-state={ofstedHeroChip.state}
>
<div className={styles.heroChipTitle}>{ofstedHeroChip.title}</div>
<div className={styles.heroChipSub}>{ofstedHeroChip.subtitle}</div>
{ofstedHeroChip.detail && (
<div className={styles.heroChipDetail}>{ofstedHeroChip.detail}</div>
)}
</div>
{admissions?.oversubscribed && (
<div className={`${styles.heroChip} ${styles['tone-coral']}`}>
<div className={styles.heroChipTitle}>Oversubscribed</div>
<div className={styles.heroChipSub}>
{admissions.first_preference_offer_pct != null
? `${Math.round(admissions.first_preference_offer_pct)}% of first-choice applicants offered a place`
: 'More applicants than places'}
</div>
</div>
)}
</div>
{/* At-a-glance stats row */}
{latestResults && (
{/* At-a-glance stats row — the single home for the headline numbers.
Shows whenever any tile has content (results, Ofsted, or admissions),
so schools without KS2/KS4 results still carry their Ofsted signal. */}
{hasHeroStats && (
<div className={styles.heroStats}>
{isPrimary && heroRwm != null && (
<div className={styles.heroStat}>
@@ -702,49 +702,8 @@
color: var(--text-primary, #1a1612);
}
/* ── Hero chips strip ────────────────────────────────── */
.heroChips {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-top: 1.25rem;
}
.heroChip {
flex: 0 0 240px;
padding: 0.75rem 1rem;
border-radius: 8px;
border-left: 3px solid var(--border-color, #e5dfd5);
background: var(--bg-secondary, #f3ede4);
color: var(--text-primary, #1a1612);
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.heroChipTitle {
font-size: 0.9375rem;
font-weight: 700;
line-height: 1.3;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.heroChipSub {
font-size: 0.75rem;
color: var(--text-secondary, #5c564d);
line-height: 1.4;
}
.heroChipDetail {
font-size: 0.75rem;
font-weight: 600;
line-height: 1.4;
margin-top: 0.1rem;
}
/* Hero tone system */
/* Hero tone system — colour tokens applied to the scorecard's serif Ofsted
number (colour only), independent of the .ofstedGrade{N} badges. */
.tone-teal {
--hero-tone: var(--accent-teal, #2d7d7d);
}
@@ -761,19 +720,6 @@
--hero-tone: var(--text-muted, #8a847a);
}
.heroChip.tone-teal,
.heroChip.tone-green,
.heroChip.tone-gold,
.heroChip.tone-coral,
.heroChip.tone-neutral {
border-left-color: var(--hero-tone);
background: color-mix(in srgb, var(--hero-tone) 10%, var(--bg-card, white));
}
.heroChip.tone-neutral {
background: var(--bg-secondary, #f3ede4);
}
/* ── Hero at-a-glance stats ──────────────────────────── */
.heroStats {
display: flex;
@@ -1170,15 +1116,6 @@
font-size: 0.75rem;
}
.heroChips {
gap: 0.5rem;
margin-top: 1rem;
}
.heroChip {
min-width: 100%;
}
.heroStats {
gap: 1rem 1.5rem;
}
@@ -192,6 +192,12 @@ export function SecondarySchoolDetailView({
const heroAtt8Nat = secondaryAvg.attainment_8_score ?? null;
const heroAcademicYear = latestResults ? formatAcademicYear(latestResults.year) : '';
// Scorecard renders if any tile has content, so a results-less school still
// shows its Ofsted signal (previously carried by the now-removed chip strip).
const hasHeroStats = heroAtt8 != null
|| ofsted != null
|| admissions?.first_preference_offer_pct != null;
return (
<div className={styles.container}>
{/* ── Header ─────────────────────────────────────── */}
@@ -265,30 +271,10 @@ export function SecondarySchoolDetailView({
</div>
</div>
{/* Hero signal chips */}
<div className={styles.heroChips}>
<div className={`${styles.heroChip} ${styles[`tone-${ofstedHeroChip.tone}`]}`}>
<div className={styles.heroChipTitle}>{ofstedHeroChip.title}</div>
<div className={styles.heroChipSub}>{ofstedHeroChip.subtitle}</div>
{ofstedHeroChip.detail && (
<div className={styles.heroChipDetail}>{ofstedHeroChip.detail}</div>
)}
</div>
{admissions?.oversubscribed && (
<div className={`${styles.heroChip} ${styles['tone-coral']}`}>
<div className={styles.heroChipTitle}>Oversubscribed</div>
<div className={styles.heroChipSub}>
{admissions.first_preference_offer_pct != null
? `${Math.round(admissions.first_preference_offer_pct)}% of first-choice applicants offered a place`
: 'More applicants than places'}
</div>
</div>
)}
</div>
{/* At-a-glance stats row */}
{latestResults && (
{/* At-a-glance stats row — the single home for the headline numbers.
Shows whenever any tile has content (results, Ofsted, or admissions),
so schools without KS4 results still carry their Ofsted signal. */}
{hasHeroStats && (
<div className={styles.heroStats}>
{heroAtt8 != null && (
<div className={styles.heroStat}>