PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m5s
PR Checks / Backend Smoke (pull_request) Successful in 8s
PR Checks / Build Backend (no push) (pull_request) Successful in 13s
PR Checks / Build Frontend (no push) (pull_request) Successful in 45s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 1m59s
The admissions card measured 1503px on a live school page — half the height of every section put together, and nearly three times the next largest — with its default view rendering as four tiles adrift in about 1080px of blank card. The cause was a layout trick meeting content it was never sized for. The admissions views are stacked in one grid cell so switching them never shifts layout, and the hidden ones keep their box: only visibility is dropped. That works while the views are comparable. The distance view added in #102 carries a chart, a table and a map, came to 1402px against the tile grid's 316px, and pinned every other view to its height — including the one that renders by default, which nobody had clicked. Rather than only unpinning it, the detail moves out. Every other topic on the page is a section with a nav entry, and "how close did we need to live, and would we have got in?" is a topic, not a variant reading of the intake figures. The headline number stays on the Admissions tile where the intake story is; the record behind it now lives in a Distance section directly below. admissions 1503px -> 554px distance new -> 743px (median section on the page is ~528px) Three further changes, each of which also makes the content better rather than only shorter: * The map renders on request. Before a postcode is entered it is a circle drawn round a school, and it costs a Leaflet bundle and 240px to say so; a successful check opens it automatically, which is the point at which it starts answering something. Map height 320px -> 240px. * The chart appears only at the four published years that let the summary state a direction. Below that we already refuse to call the series a trend, and a line through three points asserts one regardless of what the sentence beneath it admits. The table carries those years anyway, with the reasons a line cannot show. * Three caveat paragraphs become one. They said walking-route twice and made the same point about priorities in two voices. It now sits in CutoffDistanceDetail rather than inside the check, so it still renders for a school with coordinates missing, where there is a table but no map and no check. The new e2e guard asserts no section exceeds 2.5x the median section height. Measuring the card's internals cannot catch this: the tile grid is flex: 1, so it absorbs the stretch and every box still looks full. The first version of this test targeted an arbitrary primary, passed against the live bug, and proved nothing; pointed at a school that actually holds cut-off history it fails on staging with "#admissions is 1459px against a 526px median". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WDvkyqqHABm4bmth2kjAxE
2491 lines
57 KiB
CSS
2491 lines
57 KiB
CSS
.container {
|
||
width: 100%;
|
||
min-width: 0;
|
||
max-width: 100%;
|
||
}
|
||
|
||
/* Standalone back link, sits above the header card on the page background. */
|
||
.topBack {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.4rem;
|
||
margin: 0 0 0.75rem;
|
||
padding: 0.25rem 0;
|
||
font-size: 1.0625rem;
|
||
font-weight: 600;
|
||
color: var(--brand-strong);
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
line-height: 1.2;
|
||
transition: color 0.15s ease;
|
||
}
|
||
|
||
.topBack:hover {
|
||
color: var(--brand-strong);
|
||
text-decoration: underline;
|
||
text-underline-offset: 2px;
|
||
}
|
||
|
||
/* Header Section */
|
||
.header {
|
||
position: relative;
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
/* Padding lives on .headerContent so the map band can bleed to the edges. */
|
||
padding: 0;
|
||
margin-bottom: 0;
|
||
box-shadow: var(--shadow-soft);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.headerContent {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
gap: 1.5rem;
|
||
padding: 1.25rem 1.5rem;
|
||
}
|
||
|
||
/* With a map band above, slide the title up under the fade so map and title
|
||
read as one object; the Compare button floats glassy over the map. */
|
||
.headerHasMap .headerContent {
|
||
padding-top: 0;
|
||
margin-top: -0.5rem;
|
||
}
|
||
|
||
/* The title (not the whole content row) rises above the map fade. Keeping
|
||
.headerContent unpositioned matters: .actions must anchor to .header so it
|
||
floats over the map band, not over the title. */
|
||
.headerHasMap .titleSection {
|
||
position: relative;
|
||
z-index: 3;
|
||
}
|
||
|
||
.headerHasMap .actions {
|
||
position: absolute;
|
||
top: 14px;
|
||
right: 14px;
|
||
z-index: 6;
|
||
margin: 0;
|
||
/* Beat the mobile `.actions { width: 100% }` rule — a floating button
|
||
must never stretch across the title. */
|
||
width: auto;
|
||
}
|
||
|
||
.headerHasMap .actions .btnAdd {
|
||
background: var(--bg-card);
|
||
color: var(--brand-strong);
|
||
border-color: transparent;
|
||
-webkit-backdrop-filter: blur(6px);
|
||
backdrop-filter: blur(6px);
|
||
box-shadow: 0 2px 10px rgba(var(--shadow-rgb), 0.16);
|
||
}
|
||
|
||
.headerHasMap .actions .btnAdd:hover {
|
||
background: #fff;
|
||
}
|
||
|
||
/* Full label by default; phones over the map get an icon-only button
|
||
(same compact treatment as the section-nav compare icon). */
|
||
.btnCompareGlyph {
|
||
display: none;
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.headerHasMap .actions .btnCompareLabel {
|
||
display: none;
|
||
}
|
||
|
||
.headerHasMap .actions .btnCompareGlyph {
|
||
display: inline;
|
||
}
|
||
|
||
.headerHasMap .actions .btnAdd,
|
||
.headerHasMap .actions .btnRemove {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex: none;
|
||
width: 40px;
|
||
height: 40px;
|
||
padding: 0;
|
||
border-radius: 999px;
|
||
font-size: 1.375rem;
|
||
line-height: 1;
|
||
}
|
||
}
|
||
|
||
/* Inline "View on map ↗" trigger next to the address. */
|
||
.mapLink {
|
||
border: none;
|
||
background: none;
|
||
padding: 0;
|
||
font: inherit;
|
||
font-weight: 600;
|
||
color: var(--brand-strong);
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.mapLink:hover {
|
||
color: var(--brand-strong);
|
||
text-decoration: underline;
|
||
text-underline-offset: 2px;
|
||
}
|
||
|
||
.titleSection {
|
||
flex: 1;
|
||
}
|
||
|
||
.schoolName {
|
||
font-size: clamp(2rem, 5vw, 3.25rem);
|
||
font-weight: 700;
|
||
color: var(--text-primary);
|
||
margin-bottom: 0.5rem;
|
||
line-height: 1.1;
|
||
letter-spacing: -0.01em;
|
||
font-family: var(--font-display);
|
||
overflow-wrap: break-word;
|
||
}
|
||
|
||
.meta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.5rem;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.metaItem {
|
||
font-size: 0.8125rem;
|
||
color: var(--text-secondary);
|
||
padding: 0.125rem 0.5rem;
|
||
background: var(--bg-secondary);
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.address {
|
||
font-size: 0.875rem;
|
||
color: var(--text-muted);
|
||
margin: 0 0 0.75rem;
|
||
overflow-wrap: break-word;
|
||
}
|
||
|
||
/* Expanded header details (headteacher, website, trust, pupils) */
|
||
.headerDetails {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.5rem 1.25rem;
|
||
margin-top: 0.5rem;
|
||
}
|
||
|
||
.headerDetail {
|
||
font-size: 0.8125rem;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.headerDetail strong {
|
||
color: var(--text-primary);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.headerDetail a {
|
||
color: var(--brand);
|
||
text-decoration: none;
|
||
}
|
||
|
||
.headerDetail a:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
/* "Show all details" reveal — only rendered on mobile/tablet, where the
|
||
header details block is collapsed below the fold. Hidden on desktop. */
|
||
.detailsToggle {
|
||
display: none;
|
||
align-items: center;
|
||
gap: 0.25rem;
|
||
margin-top: 0.5rem;
|
||
padding: 0;
|
||
background: none;
|
||
border: none;
|
||
font-size: 0.8125rem;
|
||
font-weight: 600;
|
||
color: var(--brand);
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* Gender split card — sits in the Pupils & Inclusion heroStatGrid */
|
||
.genderSplitValue {
|
||
font-variant-numeric: tabular-nums;
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 0.3rem;
|
||
font-family: var(--font-display);
|
||
font-size: 1.55rem;
|
||
font-weight: 700;
|
||
line-height: 1;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.genderSplitGirls {
|
||
color: var(--phase-nursery-text);
|
||
}
|
||
|
||
.genderSplitBoys {
|
||
color: var(--phase-primary-text);
|
||
}
|
||
|
||
.genderSplitLabel {
|
||
font-family: var(--font-ui);
|
||
font-size: 0.78rem;
|
||
font-weight: 500;
|
||
color: var(--text-muted);
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.genderSplitSep {
|
||
color: var(--border);
|
||
font-weight: 400;
|
||
font-size: 1.2rem;
|
||
padding: 0 0.1rem;
|
||
}
|
||
|
||
.genderBar {
|
||
display: flex;
|
||
height: 6px;
|
||
border-radius: 999px;
|
||
overflow: hidden;
|
||
background: var(--border);
|
||
width: 100%;
|
||
}
|
||
|
||
.genderBarGirls {
|
||
background: var(--phase-nursery-text);
|
||
}
|
||
|
||
.genderBarBoys {
|
||
background: var(--phase-primary);
|
||
}
|
||
|
||
.actions {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
flex-shrink: 0;
|
||
align-self: center;
|
||
}
|
||
|
||
.btnAdd,
|
||
.btnRemove {
|
||
padding: 0.75rem 1.25rem;
|
||
font-size: 0.9375rem;
|
||
font-weight: 600;
|
||
border: none;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
white-space: nowrap;
|
||
box-shadow: var(--shadow-soft, 0 2px 8px rgba(var(--shadow-rgb), 0.08));
|
||
}
|
||
|
||
.btnAdd {
|
||
background: var(--brand-strong);
|
||
color: var(--brand-on);
|
||
}
|
||
|
||
.btnAdd:hover {
|
||
background: var(--brand-stronger);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.btnRemove {
|
||
background: var(--brand);
|
||
color: var(--brand-on);
|
||
}
|
||
|
||
.btnRemove:hover {
|
||
opacity: 0.9;
|
||
}
|
||
|
||
/* ── Sticky Section Navigation ──────────────────────── */
|
||
/* Docks directly under the global header; Back and "All" stay pinned while
|
||
only the section links scroll. */
|
||
.sectionNav {
|
||
position: sticky;
|
||
top: 64px; /* global header height on desktop */
|
||
z-index: 10;
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-top: none;
|
||
border-radius: 0 0 10px 10px;
|
||
padding: 0.5rem 0.75rem;
|
||
margin-bottom: 1rem;
|
||
box-shadow: 0 2px 4px rgba(var(--shadow-rgb), 0.04);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.sectionNav {
|
||
top: 56px; /* global header is shorter on mobile */
|
||
padding: 0.4rem 0.6rem;
|
||
gap: 0.375rem;
|
||
}
|
||
}
|
||
|
||
.sectionNavBack {
|
||
flex: none;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.3rem;
|
||
padding: 0.3rem 0.625rem;
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
color: var(--brand-strong);
|
||
background: none;
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
transition: all 0.15s ease;
|
||
}
|
||
|
||
.sectionNavBack:hover {
|
||
background: var(--bg-secondary);
|
||
border-color: var(--brand);
|
||
}
|
||
|
||
/* The scrolling middle: section links only. */
|
||
.sectionNavLinks {
|
||
flex: 1;
|
||
min-width: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.375rem;
|
||
overflow-x: auto;
|
||
white-space: nowrap;
|
||
-webkit-overflow-scrolling: touch;
|
||
scrollbar-width: none;
|
||
scroll-snap-type: x proximity;
|
||
scroll-padding-inline: 0.5rem;
|
||
}
|
||
|
||
.sectionNavLinks::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
|
||
/* Right-edge fade so users see there's more to scroll to. */
|
||
@media (max-width: 640px) {
|
||
.sectionNavLinks {
|
||
-webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
|
||
mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
|
||
}
|
||
|
||
/* When scrolled to the end, drop the fade so the last item isn't dimmed. */
|
||
.sectionNavLinks.atEnd {
|
||
-webkit-mask-image: none;
|
||
mask-image: none;
|
||
}
|
||
}
|
||
|
||
.sectionNavLink {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 0.3rem 0.625rem;
|
||
font-size: 0.75rem;
|
||
font-weight: 500;
|
||
color: var(--text-secondary);
|
||
text-decoration: none;
|
||
border-radius: 4px;
|
||
transition: all 0.15s ease;
|
||
white-space: nowrap;
|
||
scroll-snap-align: start;
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.sectionNavLink,
|
||
.sectionNavBack {
|
||
min-height: 36px;
|
||
padding: 0.5rem 0.75rem;
|
||
font-size: 0.8125rem;
|
||
}
|
||
}
|
||
|
||
.sectionNavLink:hover {
|
||
background: var(--bg-secondary);
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.sectionNavLinkActive {
|
||
background: var(--brand-strong);
|
||
color: var(--brand-on);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.sectionNavLinkActive:hover {
|
||
background: var(--brand-strong);
|
||
color: var(--brand-on);
|
||
}
|
||
|
||
/* ── Mobile: the scrolling links collapse into one "section" menu button ──
|
||
(hidden on desktop, where the links fit). */
|
||
.sectionNavMenu {
|
||
display: none; /* shown only ≤640px */
|
||
flex: 1;
|
||
min-width: 0;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 0.5rem;
|
||
min-height: 38px;
|
||
padding: 0.34rem 0.7rem;
|
||
background: var(--bg-secondary);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-family: var(--font-ui);
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.sectionNavMenuCur {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.45rem;
|
||
min-width: 0;
|
||
}
|
||
|
||
.sectionNavMenuEyebrow {
|
||
flex: none;
|
||
font-size: 0.64rem;
|
||
font-weight: 600;
|
||
letter-spacing: 0.04em;
|
||
text-transform: uppercase;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.sectionNavMenuNow {
|
||
font-size: 0.85rem;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.sectionNavMenuChev {
|
||
flex: none;
|
||
color: var(--text-muted);
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
/* Compact icon version of the Compare CTA, used on mobile. */
|
||
.sectionNavCompareIcon {
|
||
display: none; /* shown only ≤640px */
|
||
position: relative;
|
||
flex: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 38px;
|
||
height: 38px;
|
||
border-radius: 9px;
|
||
border: 1px solid var(--brand-strong);
|
||
background: var(--brand-strong);
|
||
color: var(--brand-on);
|
||
cursor: pointer;
|
||
transition: background 0.15s ease, border-color 0.15s ease;
|
||
}
|
||
|
||
.sectionNavCompareIcon svg {
|
||
width: 19px;
|
||
height: 19px;
|
||
}
|
||
|
||
.sectionNavCompareBadge {
|
||
position: absolute;
|
||
top: -5px;
|
||
right: -5px;
|
||
width: 16px;
|
||
height: 16px;
|
||
border-radius: 50%;
|
||
background: var(--bg-card);
|
||
color: var(--brand-strong);
|
||
border: 1.5px solid var(--brand);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 0.7rem;
|
||
font-weight: 800;
|
||
line-height: 1;
|
||
}
|
||
|
||
.sectionNavCompareIconIn {
|
||
background: var(--bg-card);
|
||
border-color: var(--brand);
|
||
color: var(--brand);
|
||
}
|
||
|
||
/* Compare CTA carried into the bar once the hero's button scrolls away. */
|
||
.sectionNavCompare {
|
||
flex: none;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 0.34rem 0.7rem;
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
color: var(--brand-on);
|
||
background: var(--brand-strong);
|
||
border: 1px solid var(--brand-strong);
|
||
border-radius: 999px;
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
transition: all 0.15s ease;
|
||
}
|
||
|
||
.sectionNavCompare:hover {
|
||
background: var(--brand-stronger);
|
||
border-color: var(--brand-stronger);
|
||
}
|
||
|
||
.sectionNavCompareIn {
|
||
background: var(--bg-card);
|
||
color: var(--brand);
|
||
border-color: var(--brand);
|
||
}
|
||
|
||
.sectionNavCompareIn:hover {
|
||
background: var(--bg-secondary);
|
||
border-color: var(--brand);
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.sectionNavCompare {
|
||
min-height: 36px;
|
||
}
|
||
}
|
||
|
||
/* "All ▾" jump menu (desktop). */
|
||
.sectionNavAll {
|
||
flex: none;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.25rem;
|
||
padding: 0.34rem 0.65rem;
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
color: var(--text-primary);
|
||
background: var(--bg-secondary);
|
||
border: none;
|
||
border-radius: 999px;
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
transition: background 0.15s ease;
|
||
}
|
||
|
||
.sectionNavAll:hover {
|
||
background: var(--border);
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.sectionNavAll {
|
||
min-height: 36px;
|
||
}
|
||
}
|
||
|
||
.sectionsBackdrop {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 1500;
|
||
background: rgba(var(--shadow-rgb), 0.28);
|
||
}
|
||
|
||
.sectionsPanel {
|
||
position: absolute;
|
||
top: calc(100% + 6px);
|
||
right: 0;
|
||
z-index: 1600;
|
||
width: 230px;
|
||
max-height: min(70vh, 460px);
|
||
overflow-y: auto;
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
box-shadow: 0 18px 44px rgba(var(--shadow-rgb), 0.2);
|
||
padding: 0.35rem;
|
||
}
|
||
|
||
.sectionsPanelHead {
|
||
font-family: var(--font-display);
|
||
font-size: 0.9rem;
|
||
font-weight: 600;
|
||
color: var(--text-primary);
|
||
padding: 0.4rem 0.6rem 0.5rem;
|
||
}
|
||
|
||
.sectionsItem {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 0.5rem;
|
||
padding: 0.55rem 0.6rem;
|
||
border-radius: 8px;
|
||
font-size: 0.85rem;
|
||
color: var(--text-secondary);
|
||
text-decoration: none;
|
||
transition: background 0.12s ease;
|
||
}
|
||
|
||
.sectionsItem:hover {
|
||
background: var(--bg-secondary);
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.sectionsItemActive {
|
||
background: var(--brand-bg);
|
||
color: var(--brand-strong);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.sectionsTick {
|
||
color: var(--brand-strong);
|
||
}
|
||
|
||
/* On phones the menu becomes a bottom sheet. */
|
||
@media (max-width: 640px) {
|
||
.sectionsPanel {
|
||
position: fixed;
|
||
top: auto;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
width: auto;
|
||
max-height: 74vh;
|
||
border-radius: 16px 16px 0 0;
|
||
padding: 0.5rem 0.6rem calc(0.8rem + env(safe-area-inset-bottom, 0));
|
||
box-shadow: 0 -10px 40px rgba(var(--shadow-rgb), 0.25);
|
||
}
|
||
|
||
.sectionsItem {
|
||
padding: 0.7rem 0.6rem;
|
||
font-size: 0.9rem;
|
||
}
|
||
}
|
||
|
||
/* Swap which controls show at the mobile breakpoint. Declared last so these
|
||
display rules win over the base (equal-specificity) declarations above. */
|
||
@media (max-width: 640px) {
|
||
.sectionNavLinks,
|
||
.sectionNavCompare,
|
||
.sectionNavAll,
|
||
.sectionNavBackLabel {
|
||
display: none;
|
||
}
|
||
|
||
.sectionNavMenu {
|
||
display: flex;
|
||
}
|
||
|
||
.sectionNavCompareIcon {
|
||
display: inline-flex;
|
||
}
|
||
}
|
||
|
||
/* Unified card for all content sections */
|
||
.card {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
padding: 1.25rem 1.5rem;
|
||
margin-bottom: 1rem;
|
||
box-shadow: var(--shadow-soft);
|
||
scroll-margin-top: 6rem;
|
||
min-width: 0;
|
||
max-width: 100%;
|
||
}
|
||
|
||
/* Section Title */
|
||
.sectionTitle {
|
||
font-size: 1.125rem;
|
||
font-weight: 600;
|
||
color: var(--text-primary);
|
||
margin-bottom: 0.875rem;
|
||
padding-bottom: 0.5rem;
|
||
border-bottom: 2px solid var(--border);
|
||
font-family: var(--font-display);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.375rem;
|
||
flex-wrap: wrap;
|
||
overflow-wrap: break-word;
|
||
min-width: 0;
|
||
}
|
||
|
||
.sectionTitle::before {
|
||
content: "";
|
||
display: inline-block;
|
||
width: 3px;
|
||
height: 1em;
|
||
background: var(--brand);
|
||
border-radius: 2px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.sectionSubtitle {
|
||
font-size: 0.85rem;
|
||
color: var(--text-muted);
|
||
margin: -0.5rem 0 1rem;
|
||
}
|
||
|
||
.subSectionTitle {
|
||
font-size: 0.875rem;
|
||
font-weight: 600;
|
||
color: var(--text-secondary);
|
||
margin: 1.25rem 0 0.75rem;
|
||
}
|
||
|
||
/* Metrics Grid & Cards */
|
||
.metricsGrid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||
gap: 0.75rem;
|
||
}
|
||
|
||
.metricCard {
|
||
background: var(--bg-secondary);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
padding: 0.75rem;
|
||
text-align: center;
|
||
}
|
||
|
||
.metricLabel {
|
||
font-size: 0.6875rem;
|
||
color: var(--text-muted);
|
||
margin-bottom: 0.25rem;
|
||
font-weight: 500;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.03em;
|
||
}
|
||
|
||
.metricValue {
|
||
font-variant-numeric: tabular-nums;
|
||
font-size: 1.25rem;
|
||
font-weight: 700;
|
||
color: var(--text-primary);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 0.25rem;
|
||
overflow-wrap: break-word;
|
||
word-break: break-word;
|
||
}
|
||
|
||
/* Last distance offered, secondary template. These cards are opaque
|
||
(--bg-secondary), so the tint can be set directly here — unlike the primary
|
||
tile grid, nothing shows through between them. */
|
||
.metricCardDistance {
|
||
background-color: var(--bg-card);
|
||
background-image: linear-gradient(var(--brand-bg), var(--brand-bg));
|
||
border-color: var(--brand);
|
||
/* Two columns' worth of width. Its label carries the entry year as well as
|
||
the measure, which at the 140px track width of the other cards wraps to
|
||
three lines and pushes the figure out of alignment with them. */
|
||
grid-column: span 2;
|
||
}
|
||
|
||
.metricCardDistance .metricValue {
|
||
flex-direction: column;
|
||
gap: 0.1rem;
|
||
color: var(--brand-strong);
|
||
}
|
||
|
||
.metricValueSub {
|
||
font-size: 0.75rem;
|
||
font-weight: 500;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
/* Same measured reason as .admissionsTileDistance above: --text-muted falls to
|
||
4.09:1 on the tinted card in dark theme. */
|
||
.metricCardDistance .metricLabel,
|
||
.metricCardDistance .metricValueSub {
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.metricHint {
|
||
font-size: 0.7rem;
|
||
color: var(--text-muted);
|
||
margin-top: 0.3rem;
|
||
font-style: italic;
|
||
}
|
||
|
||
/* ── Hero stat cards (RWM combined, Pupils & Inclusion, etc.) ── */
|
||
/* Larger brand-tinted cards with display-weight numbers — reserved for
|
||
the top-of-section headline metrics. Use .metricCard for denser
|
||
secondary metrics. */
|
||
.heroStatGrid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 0.85rem;
|
||
margin-bottom: 0.25rem;
|
||
}
|
||
|
||
.heroStatCard {
|
||
font-variant-numeric: tabular-nums;
|
||
background: var(--status-above-bg);
|
||
border: 1px solid rgba(var(--status-above-rgb), 0.2);
|
||
border-radius: 12px;
|
||
padding: 1rem 1.1rem;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.4rem;
|
||
text-align: left;
|
||
}
|
||
|
||
.heroStatLabel {
|
||
font-size: 0.6rem;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.08em;
|
||
color: var(--text-muted);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.3rem;
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.heroStatValue {
|
||
font-variant-numeric: tabular-nums;
|
||
font-family: var(--font-display);
|
||
font-size: 2.1rem;
|
||
font-weight: 700;
|
||
line-height: 1;
|
||
color: var(--status-above);
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 0.5rem;
|
||
flex-wrap: wrap;
|
||
justify-content: flex-start;
|
||
}
|
||
|
||
.heroStatHint {
|
||
font-variant-numeric: tabular-nums;
|
||
font-size: 0.7rem;
|
||
color: var(--text-muted);
|
||
font-style: normal;
|
||
margin-top: 0;
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
.heroStatGrid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.heroStatValue {
|
||
font-size: 1.85rem;
|
||
}
|
||
}
|
||
|
||
/* Progress score colour coding */
|
||
.progressPositive {
|
||
color: var(--status-above);
|
||
font-weight: 700;
|
||
}
|
||
|
||
.progressNegative {
|
||
color: var(--status-below);
|
||
font-weight: 700;
|
||
}
|
||
|
||
/* ── Semantic status colours (unified) ────────────── */
|
||
.statusGood {
|
||
background: var(--status-above-bg);
|
||
color: var(--status-above);
|
||
}
|
||
|
||
.statusWarn {
|
||
background: var(--status-below-bg);
|
||
color: var(--status-below);
|
||
}
|
||
|
||
.statusBad {
|
||
background: var(--status-below-bg);
|
||
color: var(--status-below);
|
||
}
|
||
|
||
/* Charts Section */
|
||
.chartContainer {
|
||
width: 100%;
|
||
/* Taller on desktop so the trend lines have vertical room to separate
|
||
and read clearly. Mobile overrides this to height:auto below (the
|
||
max-width:768px query), so this only affects desktop. */
|
||
height: 380px;
|
||
position: relative;
|
||
}
|
||
|
||
/* Detailed Metrics - Compact Grid Layout */
|
||
.metricGroupsGrid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 1rem;
|
||
}
|
||
|
||
.metricGroup {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.metricGroupTitle {
|
||
font-size: 0.875rem;
|
||
font-weight: 600;
|
||
color: var(--text-primary);
|
||
margin-bottom: 0.5rem;
|
||
padding-bottom: 0.375rem;
|
||
border-bottom: 1px solid var(--border);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.375rem;
|
||
}
|
||
|
||
.metricTable {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.375rem;
|
||
}
|
||
|
||
.metricRow {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 0.375rem 0.625rem;
|
||
background: var(--bg-secondary);
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.metricName {
|
||
font-size: 0.75rem;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.metricRow .metricValue {
|
||
font-size: 0.875rem;
|
||
font-weight: 600;
|
||
color: var(--status-above);
|
||
}
|
||
|
||
/* History Table */
|
||
.tableWrapper {
|
||
overflow-x: auto;
|
||
max-width: 100%;
|
||
margin-top: 0.5rem;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
|
||
.historicalSubtitle {
|
||
font-size: 0.8rem;
|
||
color: var(--text-muted);
|
||
margin: 1.25rem 0 0.25rem;
|
||
}
|
||
|
||
.dataTable {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 0.8125rem;
|
||
}
|
||
|
||
.dataTable thead {
|
||
background: var(--bg-secondary);
|
||
}
|
||
|
||
.dataTable th {
|
||
padding: 0.625rem 0.75rem;
|
||
text-align: left;
|
||
font-weight: 600;
|
||
font-size: 0.6875rem;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.03em;
|
||
color: var(--text-primary);
|
||
border-bottom: 2px solid var(--border);
|
||
}
|
||
|
||
.dataTable td {
|
||
padding: 0.5rem 0.75rem;
|
||
border-bottom: 1px solid var(--border);
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.dataTable tbody tr:last-child td {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.dataTable tbody tr:hover {
|
||
background: var(--bg-secondary);
|
||
}
|
||
|
||
.yearCell {
|
||
font-weight: 600;
|
||
color: var(--status-below);
|
||
}
|
||
|
||
/* Ofsted */
|
||
.ofstedHeader {
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 0.75rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.ofstedGrade {
|
||
display: inline-block;
|
||
padding: 0.35rem 0.75rem;
|
||
font-size: 1rem;
|
||
line-height: 1.4;
|
||
font-weight: 700;
|
||
border-radius: 6px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.ofstedGrade1 {
|
||
background: var(--status-above-bg);
|
||
color: var(--status-above);
|
||
}
|
||
.ofstedGrade2 {
|
||
background: rgba(var(--status-above-rgb), 0.12);
|
||
color: var(--status-above);
|
||
}
|
||
.ofstedGrade3 {
|
||
background: var(--status-below-bg);
|
||
color: var(--status-below);
|
||
}
|
||
.ofstedGrade4 {
|
||
background: var(--status-below);
|
||
color: var(--text-inverse);
|
||
}
|
||
|
||
/* Report Card grade colours (5-level scale, lower = better) */
|
||
.rcGrade1 {
|
||
background: var(--status-above-bg);
|
||
color: var(--status-above);
|
||
} /* Exceptional */
|
||
.rcGrade2 {
|
||
background: rgba(var(--status-above-rgb), 0.12);
|
||
color: var(--status-above);
|
||
} /* Strong */
|
||
.rcGrade3 {
|
||
background: var(--status-below-bg);
|
||
color: var(--status-below);
|
||
} /* Expected standard */
|
||
.rcGrade4 {
|
||
background: rgba(var(--status-below-rgb), 0.12);
|
||
color: var(--status-below);
|
||
} /* Needs attention */
|
||
.rcGrade5 {
|
||
background: var(--status-below);
|
||
color: var(--text-inverse);
|
||
} /* Urgent improvement */
|
||
|
||
/* Safeguarding value (used inside a standard metricCard) */
|
||
.safeguardingMet {
|
||
display: inline-block;
|
||
padding: 0.2rem 0.6rem;
|
||
border-radius: 4px;
|
||
font-size: 0.8125rem;
|
||
font-weight: 600;
|
||
background: var(--status-above-bg);
|
||
color: var(--status-above);
|
||
}
|
||
.safeguardingNotMet {
|
||
display: inline-block;
|
||
padding: 0.2rem 0.6rem;
|
||
border-radius: 4px;
|
||
font-size: 0.8125rem;
|
||
font-weight: 700;
|
||
background: var(--brand-bg);
|
||
color: var(--brand-strong);
|
||
}
|
||
|
||
/* ── Ofsted grade grids (Report Card + OEIF) ──
|
||
Uniform, vertically-aligned grade chips. Labels reserve two lines so
|
||
single- and double-line labels put their chips on the same baseline;
|
||
every chip (Met, Strong, Expected standard, …) shares one font size,
|
||
padding and min-height regardless of how many lines its text wraps to. */
|
||
.gradeGrid .metricCard {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
padding: 0.85rem 0.75rem;
|
||
}
|
||
.gradeGrid .metricLabel {
|
||
min-height: 2.6em;
|
||
margin: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text-align: center;
|
||
}
|
||
.gradeGrid .metricValue {
|
||
margin-top: auto;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
max-width: 100%;
|
||
min-height: 2.6em;
|
||
padding: 0.3rem 0.7rem;
|
||
border-radius: 5px;
|
||
font-size: 1rem;
|
||
font-weight: 700;
|
||
line-height: 1.25;
|
||
text-align: center;
|
||
}
|
||
|
||
.ofstedDisclaimer {
|
||
font-size: 0.8rem;
|
||
color: var(--text-muted);
|
||
font-style: italic;
|
||
margin: 0 0 1rem;
|
||
}
|
||
|
||
.ofstedDate {
|
||
font-size: 0.85rem;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.ofstedPrevious {
|
||
font-size: 0.8125rem;
|
||
color: var(--text-muted);
|
||
font-style: italic;
|
||
}
|
||
|
||
.ofstedReportLink {
|
||
font-size: 0.8125rem;
|
||
color: var(--brand);
|
||
text-decoration: none;
|
||
margin-left: auto;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.ofstedReportLink:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
/* Admissions badge — uses unified status colours */
|
||
.admissionsBadge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.35rem;
|
||
padding: 0.3rem 0.75rem;
|
||
border-radius: 6px;
|
||
font-size: 0.8125rem;
|
||
font-weight: 600;
|
||
margin-top: 0.75rem;
|
||
}
|
||
|
||
/* Deprivation dot scale */
|
||
.deprivationDots {
|
||
display: flex;
|
||
gap: 0.375rem;
|
||
margin: 0.75rem 0 0.5rem;
|
||
align-items: center;
|
||
}
|
||
|
||
.deprivationDot {
|
||
width: 1.25rem;
|
||
height: 1.25rem;
|
||
border-radius: 50%;
|
||
background: var(--bg-secondary);
|
||
border: 2px solid var(--border);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.deprivationDotFilled {
|
||
background: var(--brand);
|
||
border-color: var(--brand);
|
||
}
|
||
|
||
.deprivationDesc {
|
||
font-size: 0.875rem;
|
||
color: var(--text-secondary);
|
||
line-height: 1.5;
|
||
margin: 0;
|
||
}
|
||
|
||
.deprivationScaleLabel {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
font-size: 0.7rem;
|
||
color: var(--text-muted);
|
||
margin-top: 0.25rem;
|
||
}
|
||
|
||
/* Progress note */
|
||
.ofstedAllSame {
|
||
font-size: 0.9375rem;
|
||
color: var(--text-secondary);
|
||
margin: 0.5rem 0 0;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.ofstedAllSame strong {
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.progressNote {
|
||
margin-top: 0.75rem;
|
||
font-size: 0.8rem;
|
||
color: var(--text-muted);
|
||
font-style: italic;
|
||
}
|
||
|
||
/* ── Responsive ──────────────────────────────────────── */
|
||
@media (max-width: 768px) {
|
||
.headerContent {
|
||
flex-direction: column;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.actions {
|
||
width: 100%;
|
||
}
|
||
|
||
.btnAdd,
|
||
.btnRemove {
|
||
flex: 1;
|
||
}
|
||
|
||
.schoolName {
|
||
font-size: 1.25rem;
|
||
word-break: break-word;
|
||
}
|
||
|
||
/* Pills wrap horizontally instead of stacking — short tokens like
|
||
"Manchester" / "Voluntary aided" fit 2 per row instead of 3 full
|
||
rows of empty horizontal space. */
|
||
.meta {
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
gap: 0.375rem;
|
||
}
|
||
|
||
/* Secondary header info (headteacher, website, pupil count, trust,
|
||
contact, area) isn't needed above the fold on phones/tablets, so it's
|
||
collapsed by default and revealed on demand via the "Show all details"
|
||
link — reclaiming the vertical space so the metrics surface sooner. */
|
||
.detailsToggle {
|
||
display: inline-flex;
|
||
}
|
||
|
||
.headerDetails {
|
||
display: none;
|
||
}
|
||
|
||
.headerDetailsOpen {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.375rem;
|
||
}
|
||
|
||
.metricsGrid {
|
||
grid-template-columns: repeat(2, 1fr);
|
||
}
|
||
|
||
.metricGroupsGrid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
/* PerformanceChart on mobile now stacks: trend banner → chip subtitle →
|
||
chip row → canvas → mini-legend → COVID footnote. The container must
|
||
flow naturally instead of clipping to a fixed 220px — PerformanceChart's
|
||
own .chartWrapper carries the canvas height. */
|
||
.chartContainer {
|
||
height: auto;
|
||
}
|
||
|
||
.dataTable {
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
.dataTable th,
|
||
.dataTable td {
|
||
padding: 0.5rem 0.375rem;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
.card {
|
||
padding: 1rem;
|
||
}
|
||
}
|
||
|
||
/* .heroStatLabel is shared by the SATs / Pupils stat cards below. */
|
||
.heroStatLabel {
|
||
font-size: 0.6875rem;
|
||
font-weight: 600;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.heroSummary {
|
||
font-size: 1rem;
|
||
margin-top: 1rem;
|
||
}
|
||
}
|
||
|
||
/* ── RWM bridge ("Why is combined lower?") ── */
|
||
.rwmBridge {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 0.75rem;
|
||
margin: 0.5rem 0 1.5rem;
|
||
padding: 0.9rem 1rem;
|
||
background: var(--bg-secondary);
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.rwmBridgeIcon {
|
||
flex-shrink: 0;
|
||
width: 22px;
|
||
height: 22px;
|
||
border-radius: 50%;
|
||
background: var(--brand);
|
||
color: var(--text-inverse);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-weight: 700;
|
||
font-size: 0.8rem;
|
||
font-family: var(--font-display);
|
||
margin-top: 0.05rem;
|
||
}
|
||
|
||
.rwmBridgeBody {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.rwmBridgeText {
|
||
font-size: 0.85rem;
|
||
color: var(--text-secondary);
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.rwmBridgeText strong {
|
||
color: var(--text-primary);
|
||
font-weight: 700;
|
||
}
|
||
|
||
.rwmBridgeMath {
|
||
display: flex;
|
||
gap: 0.35rem;
|
||
margin-top: 0.35rem;
|
||
flex-wrap: wrap;
|
||
font-family: var(--font-display);
|
||
color: var(--text-muted);
|
||
font-size: 0.78rem;
|
||
font-style: italic;
|
||
align-items: baseline;
|
||
}
|
||
|
||
.rwmBridgeMath strong {
|
||
color: var(--text-primary);
|
||
font-weight: 700;
|
||
font-style: normal;
|
||
}
|
||
|
||
.rwmBridgeMathSep {
|
||
opacity: 0.5;
|
||
}
|
||
|
||
/* ── Progress scores row (below SatsChart) ── */
|
||
.progressScoresRow {
|
||
margin-top: 1.25rem;
|
||
padding-top: 1rem;
|
||
border-top: 1px solid var(--border);
|
||
}
|
||
|
||
.progressScoresGrid {
|
||
display: flex;
|
||
gap: 1.5rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.progressScoreItem {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 0.4rem;
|
||
}
|
||
|
||
.progressScoreLabel {
|
||
font-size: 0.78rem;
|
||
font-weight: 500;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.progressScoreValue {
|
||
font-variant-numeric: tabular-nums;
|
||
font-size: 0.9rem;
|
||
font-weight: 700;
|
||
color: var(--text-primary);
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
/* ── Admissions Q&A list ── */
|
||
/* 2×2 stat tiles — number and label grouped together so the eye
|
||
doesn't travel the full card width. Hairline grid via gap + bg. */
|
||
.admissionsTiles {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 1px;
|
||
background: var(--border);
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
overflow: hidden;
|
||
margin: 0;
|
||
}
|
||
|
||
.admissionsTile {
|
||
background: var(--bg-card);
|
||
padding: 1.15rem 1.25rem;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.admissionsTileNum {
|
||
font-variant-numeric: tabular-nums;
|
||
font-family: var(--font-display);
|
||
font-size: 2.4rem;
|
||
font-weight: 700;
|
||
line-height: 1;
|
||
color: var(--text-primary);
|
||
font-variant-numeric: tabular-nums;
|
||
margin: 0;
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 0.4rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.admissionsTileSub {
|
||
font-family: var(--font-ui);
|
||
font-size: 0.85rem;
|
||
font-weight: 500;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.admissionsTileLabel {
|
||
margin: 0.55rem 0 0;
|
||
font-size: 0.78rem;
|
||
font-weight: 600;
|
||
letter-spacing: 0.04em;
|
||
text-transform: uppercase;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.admissionsTileAccent .admissionsTileNum {
|
||
color: var(--brand-strong);
|
||
}
|
||
|
||
/* ── Admissions: last distance offered ──
|
||
*
|
||
* Spans the full grid width instead of taking a fifth half-cell. Two reasons:
|
||
* the tiles are a 2-column grid, so a fifth tile would sit alone next to a gap;
|
||
* and this is the figure the section is most often opened for, which a
|
||
* half-width cell would rank below "Applied in total".
|
||
*/
|
||
.admissionsTileDistance {
|
||
grid-column: 1 / -1;
|
||
/*
|
||
* --brand-bg is translucent, and the tile grid paints --border between
|
||
* cells, so a plain `background` would let the hairline colour show through
|
||
* and read as muddy rather than tinted. The opaque card colour is laid down
|
||
* first and the tint composited over it as an image.
|
||
*/
|
||
background-color: var(--bg-card);
|
||
background-image: linear-gradient(var(--brand-bg), var(--brand-bg));
|
||
}
|
||
|
||
.admissionsTileDistance .admissionsTileNum {
|
||
color: var(--brand-strong);
|
||
}
|
||
|
||
/*
|
||
* The tint costs the muted tokens their AA margin, so this tile does not use
|
||
* them.
|
||
*
|
||
* Measured on the composited backdrop, not the computed one: the tint is a
|
||
* background-image over --bg-card, so getComputedStyle reports the untinted
|
||
* card and flatters the result. Sampling the rendered pixel in dark theme gives
|
||
* a backdrop of rgb(34,59,64), on which --text-muted is 4.09:1 — under the 4.5
|
||
* floor — while --text-secondary is 6.50:1.
|
||
*
|
||
* The label is where the year lives, so it is the last thing on the tile that
|
||
* can afford to be hard to read: a distance without its year is not a fact
|
||
* about anything.
|
||
*/
|
||
.admissionsTileDistance .admissionsTileLabel,
|
||
.admissionsTileDistance .admissionsTileSub {
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
/*
|
||
* The caveats, not a footnote in the visual-noise sense: without them the
|
||
* figure reads as a catchment boundary. Kept at body contrast rather than
|
||
* --text-muted for that reason — it qualifies the number above it, so it has
|
||
* to be as readable as the number.
|
||
*/
|
||
.admissionsDistanceNote {
|
||
margin: 0.85rem 0 0;
|
||
font-size: 0.8125rem;
|
||
line-height: 1.55;
|
||
color: var(--text-secondary);
|
||
max-width: 68ch;
|
||
}
|
||
|
||
.admissionsVerdict {
|
||
margin-top: 0.75rem;
|
||
margin-bottom: 0.25rem;
|
||
}
|
||
|
||
.admissionsVerdictHeadline {
|
||
font-family: var(--font-display);
|
||
font-size: 1.35rem;
|
||
font-weight: 700;
|
||
line-height: 1.2;
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.admissionsVerdictOver {
|
||
color: var(--brand-strong);
|
||
}
|
||
|
||
.admissionsVerdictUnder {
|
||
color: var(--status-above);
|
||
}
|
||
|
||
.admissionsVerdictSub {
|
||
font-size: 0.8rem;
|
||
color: var(--text-muted);
|
||
line-height: 1.4;
|
||
margin-top: 0.2rem;
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
.admissionsTile {
|
||
padding: 0.95rem 1rem;
|
||
}
|
||
|
||
.admissionsTileNum {
|
||
font-size: 2rem;
|
||
}
|
||
}
|
||
|
||
/* ── Admissions: header + view toggle ── */
|
||
.admissionsHeader {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 1rem;
|
||
flex-wrap: wrap;
|
||
margin-bottom: 1.25rem;
|
||
}
|
||
|
||
.admissionsHeader .sectionTitle {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.admissionsSeg {
|
||
display: inline-flex;
|
||
background: var(--bg-secondary);
|
||
border-radius: 999px;
|
||
padding: 3px;
|
||
gap: 2px;
|
||
flex: none;
|
||
}
|
||
|
||
.admissionsSeg button {
|
||
appearance: none;
|
||
border: none;
|
||
background: none;
|
||
cursor: pointer;
|
||
font: inherit;
|
||
font-size: 0.8125rem;
|
||
font-weight: 600;
|
||
color: var(--text-muted);
|
||
padding: 0.4rem 0.9rem;
|
||
border-radius: 999px;
|
||
white-space: nowrap;
|
||
transition: background 0.15s ease, color 0.15s ease;
|
||
}
|
||
|
||
.admissionsSeg button[aria-pressed="true"] {
|
||
background: var(--bg-card);
|
||
color: var(--text-primary);
|
||
box-shadow: var(--shadow-soft, 0 2px 8px rgba(var(--shadow-rgb), 0.06));
|
||
}
|
||
|
||
.admissionsSeg button:hover[aria-pressed="false"] {
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
/*
|
||
* Stack both views in one grid cell so the card sizes to the taller view —
|
||
* toggling modes never shifts layout.
|
||
*
|
||
* This only works while the views are of COMPARABLE height, and it fails
|
||
* silently rather than loudly when they are not. The hidden view keeps its
|
||
* box (display stays block below; only visibility is dropped), so the grid
|
||
* row is sized by the tallest view and every other view is padded out to
|
||
* match it. A cut-off-distance view carrying a chart, a table and a map was
|
||
* added here and came to 1402px against the tile grid's 316px, which left
|
||
* the DEFAULT view — the one nobody had clicked — as four tiles floating in
|
||
* ~1080px of blank card.
|
||
*
|
||
* Anything substantially taller than the tile grid belongs in its own
|
||
* section, not in this viewport. That is where the distance detail now
|
||
* lives; see DistanceSection.
|
||
*/
|
||
.admissionsViewport {
|
||
display: grid;
|
||
}
|
||
|
||
.admissionsViewYear,
|
||
.admissionsViewTrend {
|
||
grid-area: 1 / 1;
|
||
}
|
||
|
||
.admissionsViewYear[hidden],
|
||
.admissionsViewTrend[hidden] {
|
||
display: block;
|
||
visibility: hidden;
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* The tile grid fills the height reserved by the taller (trend) view. */
|
||
.admissionsViewYear {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.admissionsViewYear .admissionsTiles {
|
||
flex: 1;
|
||
grid-template-rows: 1fr 1fr;
|
||
}
|
||
|
||
/* The distance tile occupies a third row. Left implicit it would inherit the
|
||
1fr sizing of the row template and stretch to match the metric tiles above,
|
||
which are twice its content height. */
|
||
.admissionsViewYear .admissionsTilesWithDistance {
|
||
grid-template-rows: 1fr 1fr auto;
|
||
}
|
||
|
||
/* Cut-off distance but no EES admissions figures: there is nothing to put in
|
||
the two metric rows, and leaving them in the template paints an empty block
|
||
of the grid's gap colour beneath the tile. */
|
||
.admissionsViewYear .admissionsTilesDistanceOnly {
|
||
grid-template-rows: auto;
|
||
}
|
||
|
||
.admissionsChartCap {
|
||
font-size: 0.8125rem;
|
||
font-weight: 600;
|
||
letter-spacing: 0.04em;
|
||
text-transform: uppercase;
|
||
color: var(--text-muted);
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.admissionsTrendSummary {
|
||
font-size: 1rem;
|
||
color: var(--text-secondary);
|
||
margin: 1.25rem 0 0;
|
||
padding-top: 1.1rem;
|
||
border-top: 1px solid var(--border);
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.admissionsTrendSummary strong {
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
/* ── History accordion ── */
|
||
.historyDisclosure {
|
||
margin-top: 1rem;
|
||
}
|
||
|
||
.historyToggle {
|
||
font-size: 0.8125rem;
|
||
font-weight: 600;
|
||
color: var(--text-muted);
|
||
cursor: pointer;
|
||
padding: 0.5rem 0;
|
||
list-style: none;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.4rem;
|
||
user-select: none;
|
||
}
|
||
|
||
.historyToggle::-webkit-details-marker {
|
||
display: none;
|
||
}
|
||
|
||
.historyToggle::before {
|
||
content: "▸";
|
||
display: inline-block;
|
||
transition: transform 0.2s ease;
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
.historyDisclosure[open] > .historyToggle::before {
|
||
transform: rotate(90deg);
|
||
}
|
||
|
||
/* GIAS "Open, but proposed to close" notice strip */
|
||
.closingStrip {
|
||
background: var(--status-below-bg);
|
||
border-left: 4px solid var(--status-below-bg);
|
||
border-radius: 0 6px 6px 0;
|
||
padding: 0.55rem 0.9rem;
|
||
margin: 0.5rem 0;
|
||
font-size: 0.88rem;
|
||
color: var(--status-below);
|
||
max-width: 68ch;
|
||
}
|
||
.closingStrip strong {
|
||
color: var(--status-below);
|
||
}
|
||
/* ── Secondary-only rules, merged from SecondarySchoolDetailView.module.css ── */
|
||
.badges {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.375rem;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
.badge {
|
||
font-size: 0.8125rem;
|
||
color: var(--text-secondary);
|
||
padding: 0.125rem 0.5rem;
|
||
background: var(--bg-secondary);
|
||
border-radius: 3px;
|
||
}
|
||
.badgeSelective {
|
||
background: rgba(var(--status-below-rgb), 0.1);
|
||
color: var(--status-below);
|
||
}
|
||
.badgeFaith {
|
||
background: rgba(var(--status-above-rgb), 0.1);
|
||
color: var(--phase-secondary-text);
|
||
}
|
||
/* ── Tab Navigation (sticky) ─────────────────────────── */
|
||
.tabNav {
|
||
position: sticky;
|
||
top: 4rem;
|
||
z-index: 10;
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-top: none;
|
||
border-radius: 0 0 10px 10px;
|
||
padding: 0.5rem 1rem;
|
||
margin-bottom: 1rem;
|
||
overflow-x: auto;
|
||
white-space: nowrap;
|
||
-webkit-overflow-scrolling: touch;
|
||
scrollbar-width: none;
|
||
box-shadow: 0 2px 4px rgba(var(--shadow-rgb), 0.04);
|
||
}
|
||
.tabNav::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
.tabNavInner {
|
||
display: inline-flex;
|
||
gap: 0.25rem;
|
||
align-items: center;
|
||
}
|
||
.backBtn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 0.3rem 0.625rem;
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
color: var(--brand-strong);
|
||
background: none;
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
transition: all 0.15s ease;
|
||
margin-right: 0.25rem;
|
||
}
|
||
.backBtn:hover {
|
||
background: var(--bg-secondary);
|
||
border-color: var(--brand);
|
||
}
|
||
.tabNavDivider {
|
||
width: 1px;
|
||
height: 1rem;
|
||
background: var(--border);
|
||
margin: 0 0.25rem;
|
||
flex-shrink: 0;
|
||
}
|
||
.tabBtn {
|
||
display: inline-block;
|
||
padding: 0.3rem 0.75rem;
|
||
font-size: 0.75rem;
|
||
font-weight: 500;
|
||
color: var(--text-secondary);
|
||
background: none;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
transition: all 0.15s ease;
|
||
white-space: nowrap;
|
||
text-decoration: none;
|
||
}
|
||
.tabBtn:hover {
|
||
background: var(--bg-secondary);
|
||
color: var(--text-primary);
|
||
}
|
||
.tabBtnActive {
|
||
background: var(--brand-strong);
|
||
color: var(--brand-on);
|
||
font-weight: 600;
|
||
}
|
||
.tabBtnActive:hover {
|
||
background: var(--brand-stronger);
|
||
color: var(--brand-on);
|
||
}
|
||
/* ── Progress 8 suspension banner ───────────────────── */
|
||
.p8Banner {
|
||
background: rgba(var(--status-below-rgb), 0.1);
|
||
border: 1px solid rgba(var(--status-below-rgb), 0.3);
|
||
color: var(--status-below);
|
||
border-radius: 6px;
|
||
padding: 0.625rem 0.875rem;
|
||
font-size: 0.825rem;
|
||
margin-bottom: 1rem;
|
||
line-height: 1.5;
|
||
}
|
||
/* ── Admissions ──────────────────────────────────────── */
|
||
.admissionsTypeBadge {
|
||
border-radius: 6px;
|
||
padding: 0.5rem 0.875rem;
|
||
font-size: 0.8125rem;
|
||
margin-bottom: 1rem;
|
||
line-height: 1.5;
|
||
}
|
||
.admissionsSelective {
|
||
background: rgba(var(--status-below-rgb), 0.1);
|
||
color: var(--status-below);
|
||
border: 1px solid rgba(var(--status-below-rgb), 0.25);
|
||
}
|
||
.admissionsFaith {
|
||
background: rgba(var(--status-above-rgb), 0.08);
|
||
color: var(--phase-secondary-text);
|
||
border: 1px solid rgba(var(--status-above-rgb), 0.2);
|
||
}
|
||
.sixthFormNote {
|
||
margin-top: 1rem;
|
||
padding: 0.625rem 0.875rem;
|
||
background: var(--bg-secondary);
|
||
border-radius: 6px;
|
||
font-size: 0.825rem;
|
||
color: var(--text-secondary);
|
||
border-left: 3px solid var(--brand);
|
||
}
|
||
.genderSplitHint {
|
||
font-size: 0.7rem;
|
||
color: var(--text-muted);
|
||
margin-top: 0.35rem;
|
||
font-weight: 500;
|
||
}
|
||
/* ── Attainment 8 visual bar ─────────────────────────── */
|
||
.att8Viz {
|
||
margin: 1.25rem 0 0.5rem;
|
||
}
|
||
.att8VizLabel {
|
||
font-size: 0.6875rem;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.07em;
|
||
color: var(--text-muted);
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
.att8VizTrack {
|
||
position: relative;
|
||
height: 14px;
|
||
background: rgba(var(--status-above-rgb), 0.08);
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
overflow: visible;
|
||
}
|
||
.att8VizFill {
|
||
height: 100%;
|
||
background: var(--brand);
|
||
border-radius: 4px 0 0 4px;
|
||
transition: width 0.6s ease;
|
||
}
|
||
/*
|
||
* Knockout, for the same reason as .natTick in SatsChart.module.css: this was
|
||
* var(--brand), identical to .att8VizFill above it, so where the marker
|
||
* crossed the school's own bar it measured 1.00:1 — invisible for exactly the
|
||
* schools at or above the national average.
|
||
*
|
||
* A light core with a dark edge reads on the teal fill and on the pale track
|
||
* alike, and both tokens flip with the theme. Keep this in step with
|
||
* SatsChart's .natTick; they are the same marker on two templates.
|
||
*/
|
||
.att8VizNatLine {
|
||
position: absolute;
|
||
top: -4px;
|
||
bottom: -4px;
|
||
width: 3px;
|
||
background: var(--bg-card);
|
||
box-shadow: 0 0 0 1px var(--text-primary);
|
||
border-radius: 2px;
|
||
z-index: 2;
|
||
}
|
||
.att8VizNatPill {
|
||
position: absolute;
|
||
top: -20px;
|
||
transform: translateX(-50%);
|
||
background: var(--brand-strong);
|
||
color: var(--text-inverse);
|
||
font-size: 0.6rem;
|
||
font-weight: 700;
|
||
padding: 0.1rem 0.3rem;
|
||
border-radius: 3px;
|
||
white-space: nowrap;
|
||
}
|
||
.att8VizTicks {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-top: 0.25rem;
|
||
font-size: 0.6rem;
|
||
color: var(--text-muted);
|
||
}
|
||
/* ── Progress 8 number line ──────────────────────────── */
|
||
.p8Viz {
|
||
margin: 1.25rem 0 0.5rem;
|
||
}
|
||
.p8VizLabel {
|
||
font-size: 0.6875rem;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.07em;
|
||
color: var(--text-muted);
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
.p8VizTrack {
|
||
position: relative;
|
||
height: 14px;
|
||
background: rgba(var(--status-above-rgb), 0.06);
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
overflow: visible;
|
||
}
|
||
.p8VizCi {
|
||
position: absolute;
|
||
top: 0;
|
||
bottom: 0;
|
||
background: rgba(var(--status-above-rgb), 0.18);
|
||
border-radius: 3px;
|
||
}
|
||
.p8VizZero {
|
||
position: absolute;
|
||
top: -4px;
|
||
bottom: -4px;
|
||
width: 2px;
|
||
background: var(--border);
|
||
z-index: 1;
|
||
}
|
||
.p8VizDot {
|
||
position: absolute;
|
||
top: 50%;
|
||
transform: translate(-50%, -50%);
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
background: var(--brand);
|
||
border: 2px solid white;
|
||
box-shadow: 0 1px 4px rgba(var(--shadow-rgb), 0.2);
|
||
z-index: 3;
|
||
}
|
||
.p8VizDotNeg {
|
||
background: var(--brand);
|
||
}
|
||
.p8VizTicks {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-top: 0.25rem;
|
||
font-size: 0.6rem;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
/* ── Secondary variants ───────────────────────────────────────────────────
|
||
Three rules differ between the primary and secondary detail pages in ways
|
||
that are genuinely visual rather than incidental drift. Rather than pick a
|
||
winner (which would change one of the two pages) they are kept as explicit
|
||
variant classes, so the divergence is deliberate and reviewable instead of
|
||
accidental. Sections select them via a `variant` prop. */
|
||
|
||
/* Secondary renders a slimmer gender bar on a translucent track. */
|
||
.genderBarSecondary {
|
||
height: 4px;
|
||
background: var(--border);
|
||
margin-top: 0.45rem;
|
||
}
|
||
|
||
/* Secondary emphasises the split figures. */
|
||
.genderSplitBoysSecondary {
|
||
font-weight: 600;
|
||
}
|
||
|
||
.genderSplitGirlsSecondary {
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* Secondary hero stats sit tighter and omit the Georgia fallback. */
|
||
.heroStatValueSecondary {
|
||
font-variant-numeric: tabular-nums;
|
||
gap: 0.4rem;
|
||
font-family: var(--font-display);
|
||
}
|
||
|
||
/* ── Secondary-scoped overrides ──────────────────────────────────────────
|
||
These rules exist only in the secondary stylesheet but target class names
|
||
the primary page also uses (.card, .sectionTitle, .metricCard …). Applied
|
||
flat they would restyle the primary page, so they are scoped to a wrapper
|
||
class that only SecondarySchoolSections carries. Same principle as the
|
||
variant classes above, applied at rule scope. */
|
||
|
||
.secondaryScope .heroStatCard .heroStatLabel { font-size: 0.6rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted); }
|
||
.secondaryScope .heroStatCard .heroStatValue { font-family: var(--font-display); font-size: 2.1rem; font-weight: 700; line-height: 1; color: var(--status-above); display: flex; align-items: baseline; gap: 0.4rem; flex-wrap: wrap; }
|
||
.secondaryScope .heroStatCard .heroStatHint { font-size: 0.7rem; color: var(--text-muted); font-style: normal; margin-top: 0; }
|
||
.secondaryScope .historyDisclosure[open] .historyToggle::before { transform: rotate(90deg); }
|
||
@media (max-width: 480px) {
|
||
.secondaryScope .metricsGrid { grid-template-columns: 1fr 1fr; gap: 0.5rem; }
|
||
.secondaryScope .metricCard { padding: 0.5rem; }
|
||
.secondaryScope .metricLabel { font-size: 0.625rem; }
|
||
}
|
||
@media (max-width: 768px) {
|
||
.secondaryScope .header { padding: 1rem; }
|
||
.secondaryScope .badges { gap: 0.25rem; }
|
||
.secondaryScope .badge { font-size: 0.75rem; padding: 0.1rem 0.375rem; }
|
||
.secondaryScope .metricValue { font-size: 1rem; }
|
||
.secondaryScope .heroStatGrid { grid-template-columns: 1fr; }
|
||
.secondaryScope .heroStatCard .heroStatValue { font-size: 1.85rem; }
|
||
.secondaryScope .card { padding: 1rem; }
|
||
.secondaryScope .sectionTitle { font-size: 1rem; }
|
||
.secondaryScope .ofstedReportLink { margin-left: 0; display: block; margin-top: 0.25rem; }
|
||
.secondaryScope .admissionsTypeBadge { font-size: 0.75rem; }
|
||
}
|
||
|
||
/* ══════════════════════════════════════════════════════════════════════
|
||
Last distance offered — the Distance view, map and postcode check
|
||
══════════════════════════════════════════════════════════════════════ */
|
||
|
||
/* Visually hidden but still read aloud. The year strip encodes its verdict as
|
||
a glyph, so every chip carries a spelled-out equivalent here — a screen
|
||
reader announcing "2025 ✓" tells you nothing. */
|
||
.srOnly {
|
||
position: absolute;
|
||
width: 1px;
|
||
height: 1px;
|
||
padding: 0;
|
||
margin: -1px;
|
||
overflow: hidden;
|
||
clip-path: inset(50%);
|
||
white-space: nowrap;
|
||
border: 0;
|
||
}
|
||
|
||
.cutoffChartKey {
|
||
margin: 0.6rem 0 0;
|
||
font-size: 0.8125rem;
|
||
line-height: 1.55;
|
||
color: var(--text-secondary);
|
||
max-width: 68ch;
|
||
}
|
||
|
||
/* ── Year-by-year table ── */
|
||
|
||
.cutoffTableWrap {
|
||
margin-top: 1.25rem;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
.cutoffTable {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 0.875rem;
|
||
/* Below this the four columns crush; the wrapper scrolls instead of the
|
||
page, so the section itself never scrolls sideways. */
|
||
min-width: 26rem;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
.cutoffTable thead th {
|
||
text-align: right;
|
||
font-size: 0.6875rem;
|
||
font-weight: 600;
|
||
letter-spacing: 0.04em;
|
||
text-transform: uppercase;
|
||
color: var(--text-secondary);
|
||
padding: 0 0.625rem 0.55rem;
|
||
border-bottom: 1px solid var(--border);
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.cutoffTable thead th:first-child,
|
||
.cutoffTable tbody th {
|
||
text-align: left;
|
||
}
|
||
|
||
.cutoffTable tbody td,
|
||
.cutoffTable tbody th {
|
||
text-align: right;
|
||
padding: 0.65rem 0.625rem;
|
||
border-bottom: 1px solid var(--border);
|
||
font-weight: 400;
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.cutoffTable tbody th {
|
||
font-weight: 600;
|
||
}
|
||
|
||
.cutoffTable tbody tr:last-child td,
|
||
.cutoffTable tbody tr:last-child th {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.cutoffRowLatest td,
|
||
.cutoffRowLatest th {
|
||
background: var(--bg-secondary);
|
||
}
|
||
|
||
.cutoffCellMissing {
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.cutoffRouteFlag {
|
||
font-size: 0.75rem;
|
||
color: var(--text-secondary);
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.cutoffPill {
|
||
display: inline-block;
|
||
font-size: 0.6875rem;
|
||
font-weight: 600;
|
||
padding: 0.15rem 0.55rem;
|
||
border-radius: 999px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.cutoffPillOver {
|
||
background: var(--brand-bg);
|
||
color: var(--brand-strong);
|
||
}
|
||
|
||
.cutoffPillOk {
|
||
background: var(--status-above-bg);
|
||
color: var(--status-above);
|
||
}
|
||
|
||
.cutoffPillNa {
|
||
background: var(--bg-secondary);
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
/* ── Map panel ── */
|
||
|
||
.cutoffMapPanel {
|
||
margin-top: 1.75rem;
|
||
padding-top: 1.5rem;
|
||
border-top: 1px solid var(--border);
|
||
}
|
||
|
||
|
||
.cutoffMapFigure {
|
||
/* Enough to read a set of concentric rings and no more — this is a
|
||
diagram of a number, not a map anyone navigates by. */
|
||
height: 240px;
|
||
border-radius: var(--radius-md);
|
||
overflow: hidden;
|
||
border: 1px solid var(--border);
|
||
background: var(--bg-secondary);
|
||
}
|
||
|
||
.cutoffMapSkeleton {
|
||
width: 100%;
|
||
height: 100%;
|
||
background: var(--bg-secondary);
|
||
}
|
||
|
||
.cutoffMapLegend {
|
||
list-style: none;
|
||
margin: 0.85rem 0 0;
|
||
padding: 0;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.5rem 1.25rem;
|
||
font-size: 0.8125rem;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.cutoffMapLegend li {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.45rem;
|
||
}
|
||
|
||
.cutoffSwatch {
|
||
width: 14px;
|
||
height: 14px;
|
||
border-radius: 50%;
|
||
flex: none;
|
||
}
|
||
|
||
.cutoffSwatchNow {
|
||
background: var(--brand-bg);
|
||
box-shadow: inset 0 0 0 2px var(--brand);
|
||
}
|
||
|
||
.cutoffSwatchPast {
|
||
background: transparent;
|
||
box-shadow: inset 0 0 0 1.5px var(--brand);
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.cutoffSwatchHome {
|
||
width: 11px;
|
||
height: 11px;
|
||
border-radius: 2px;
|
||
background: var(--status-above);
|
||
transform: rotate(45deg);
|
||
}
|
||
|
||
.cutoffMapCaveat {
|
||
margin: 0.75rem 0 0;
|
||
font-size: 0.8125rem;
|
||
line-height: 1.55;
|
||
color: var(--text-secondary);
|
||
max-width: 68ch;
|
||
}
|
||
|
||
/* ── Postcode check ── */
|
||
|
||
/* The check now leads the panel, so it carries no divider of its own — the
|
||
rule above it belonged to a map that used to sit there. */
|
||
.cutoffCheck {
|
||
margin-top: 0;
|
||
}
|
||
|
||
.cutoffCheckTitle {
|
||
margin: 0 0 0.25rem;
|
||
font-family: var(--font-display);
|
||
/* Now the panel's only heading (an h3), so it carries the weight the
|
||
retired .cutoffMapTitle used to. */
|
||
font-size: 1.1rem;
|
||
font-weight: 700;
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.cutoffCheckSub {
|
||
margin: 0 0 0.85rem;
|
||
font-size: 0.8125rem;
|
||
line-height: 1.55;
|
||
color: var(--text-secondary);
|
||
max-width: 62ch;
|
||
}
|
||
|
||
.cutoffCheckForm {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.cutoffCheckInput {
|
||
font: inherit;
|
||
font-size: 1rem;
|
||
padding: 0.6rem 0.8rem;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
background: var(--bg-card);
|
||
color: var(--text-primary);
|
||
text-transform: uppercase;
|
||
/* Grows into the row but never past it — at 320px a fixed width would push
|
||
the button out of the viewport. */
|
||
flex: 1 1 9rem;
|
||
/* Stops well short of full width on desktop, where a postcode field
|
||
spanning the whole card reads as a search box. */
|
||
max-width: 16rem;
|
||
min-width: 0;
|
||
}
|
||
|
||
.cutoffCheckInput::placeholder {
|
||
text-transform: none;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.cutoffCheckInput:focus-visible {
|
||
outline: 2px solid var(--brand);
|
||
outline-offset: 1px;
|
||
border-color: var(--brand);
|
||
}
|
||
|
||
.cutoffCheckButton {
|
||
font: inherit;
|
||
font-weight: 600;
|
||
font-size: 1rem;
|
||
padding: 0.6rem 1.25rem;
|
||
border: none;
|
||
border-radius: var(--radius-md);
|
||
background: var(--brand);
|
||
color: var(--brand-on);
|
||
cursor: pointer;
|
||
flex: none;
|
||
transition: background var(--transition);
|
||
}
|
||
|
||
.cutoffCheckButton:hover:not(:disabled) {
|
||
background: var(--brand-strong);
|
||
}
|
||
|
||
.cutoffCheckButton:disabled {
|
||
opacity: 0.65;
|
||
cursor: progress;
|
||
}
|
||
|
||
.cutoffCheckButton:focus-visible {
|
||
outline: 2px solid var(--brand);
|
||
outline-offset: 2px;
|
||
}
|
||
|
||
.cutoffCheckError {
|
||
margin: 0.75rem 0 0;
|
||
font-size: 0.875rem;
|
||
color: var(--status-below);
|
||
}
|
||
|
||
.cutoffCheckResult {
|
||
margin-top: 0.9rem;
|
||
padding: 0.9rem 1rem;
|
||
border-radius: var(--radius-md);
|
||
background-color: var(--bg-card);
|
||
background-image: linear-gradient(var(--brand-bg), var(--brand-bg));
|
||
}
|
||
|
||
.cutoffCheckHeadline {
|
||
margin: 0;
|
||
font-family: var(--font-display);
|
||
font-size: 1.05rem;
|
||
font-weight: 700;
|
||
line-height: 1.35;
|
||
color: var(--text-primary);
|
||
text-wrap: balance;
|
||
}
|
||
|
||
.cutoffCheckDetail {
|
||
margin: 0.35rem 0 0;
|
||
font-size: 0.8125rem;
|
||
line-height: 1.55;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.cutoffYearStrip {
|
||
list-style: none;
|
||
margin: 0.75rem 0 0;
|
||
padding: 0;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.3rem;
|
||
}
|
||
|
||
.cutoffYearChip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.3rem;
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
padding: 0.2rem 0.5rem;
|
||
border-radius: 999px;
|
||
font-variant-numeric: tabular-nums;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.cutoffYearChipYear {
|
||
font-weight: 700;
|
||
}
|
||
|
||
.cutoffYearInside {
|
||
background: var(--status-above-bg);
|
||
color: var(--status-above);
|
||
}
|
||
|
||
.cutoffYearOutside {
|
||
background: var(--status-below-bg);
|
||
color: var(--status-below);
|
||
}
|
||
|
||
/* Deliberately neutral rather than a warning colour: "we cannot tell" is not a
|
||
bad outcome, and tinting it amber would read as one. */
|
||
.cutoffYearTooClose {
|
||
background: var(--bg-secondary);
|
||
color: var(--text-primary);
|
||
box-shadow: inset 0 0 0 1px var(--border);
|
||
}
|
||
|
||
.cutoffYearUnknown {
|
||
background: var(--bg-secondary);
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.cutoffCheckCaveat {
|
||
margin: 0.9rem 0 0;
|
||
font-size: 0.8125rem;
|
||
line-height: 1.55;
|
||
color: var(--text-secondary);
|
||
max-width: 68ch;
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.cutoffMapFigure {
|
||
height: 200px;
|
||
}
|
||
}
|
||
|
||
/* ── Map, revealed on request ── */
|
||
|
||
.cutoffMapToggle {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.4rem;
|
||
margin-top: 1rem;
|
||
padding: 0.5rem 0.9rem;
|
||
font: inherit;
|
||
font-size: 0.875rem;
|
||
font-weight: 600;
|
||
color: var(--brand-strong);
|
||
background: none;
|
||
border: 1px solid var(--border);
|
||
border-radius: 999px;
|
||
cursor: pointer;
|
||
transition: border-color var(--transition), background var(--transition);
|
||
}
|
||
|
||
.cutoffMapToggle:hover {
|
||
border-color: var(--brand);
|
||
background: var(--brand-bg);
|
||
}
|
||
|
||
.cutoffMapToggle:focus-visible {
|
||
outline: 2px solid var(--brand);
|
||
outline-offset: 2px;
|
||
}
|
||
|
||
.cutoffMapReveal {
|
||
margin-top: 1rem;
|
||
}
|