PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m2s
PR Checks / Backend Smoke (pull_request) Successful in 7s
PR Checks / Build Backend (no push) (pull_request) Successful in 11s
PR Checks / Build Frontend (no push) (pull_request) Successful in 48s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 1m6s
Three things, all reported from the live mobile view.
WE ARE NOT AN OFFICIAL SERVICE
The value prop read "Official & trusted", whose grammatical subject is this
site — it reads as a claim that schoolcompare is an official service. It is
not; it is an independent site that republishes official figures. Now "Built
on official data", which describes the data instead.
Checked every other use of the word: all nine describe the data ("official DfE
figures", "the official figure isn't in") and are correct as they stand. That
one title was the only place the site described itself.
The footer now says it outright — "An independent site. Not affiliated with
the Department for Education or Ofsted." — so it appears on every page rather
than being left to inference. An e2e test asserts both halves: that the
statement is present, and that no text presents the site itself as official.
The disclaimer first measured 4.71:1 against a 4.5 floor. That is a fine
margin for decoration and the wrong one for a line whose job is to be legible
to someone checking whether this is a government site; it is now 5.59:1,
quieter than the copy around it by size rather than by contrast.
THE ARTWORK SAT UNDER THE SEARCH ON PHONES
The DOM keeps .heroContent first so the desktop overlay does not depend on
source order, which left the band stranded at the bottom of the panel, reading
as a strip stuck on the end rather than a hero image. `order` moves it above
the copy on phones; it is decorative and aria-hidden, so no reading order
changes. Measured on a 667px viewport — the shortest phone still in use — the
search button lands at 418px, comfortably inside the fold.
THE SEARCH INPUT WAS UNUSABLE ON PHONES
"Search schools" is a fixed 134px with white-space: nowrap, so it took 48% of
the row. Measured on staging:
390px input text space 102px placeholder needs 194px
360px 72px
320px 32px
At 320px you could not see what you were typing. The button now wraps to its
own full-width row below 480px, which fixes 360px and up — 390px goes from
102px to 226px of text space. The pill stays a single element, so it keeps its
border, shadow and :focus-within ring, and the button gains a full-width tap
target.
320px is still ~30px short. Closing it needs a shorter placeholder, which three
other tests match on by exact string; left alone deliberately rather than
churn them for a width that is effectively gone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1583 lines
34 KiB
CSS
1583 lines
34 KiB
CSS
.homeView {
|
|
width: 100%;
|
|
}
|
|
|
|
/* ── Page rhythm ───────────────────────────────────────────────────────────
|
|
The landing page owns the vertical gap between its bands; no band sets its
|
|
own top or bottom margin. Previously each one did, and the gaps came out as
|
|
24 / 32 / 24 / 16 / 48 / 32 / 16px — no scale, which is most of the reason a
|
|
designed page read as a stack of unrelated strips. If you add a band here,
|
|
give it padding and a ground, never a margin. */
|
|
.landing {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--section-gap);
|
|
}
|
|
|
|
/* The hero and the four reasons under it are one thought, so they sit closer
|
|
to each other than to the next band. This is the only place that overrides
|
|
the page gap, and it does it by grouping rather than by re-spacing. */
|
|
.heroGroup {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2rem;
|
|
}
|
|
|
|
/* ── Section header ────────────────────────────────────────────────────────
|
|
One pattern for every band: kicker, heading, optional aside. */
|
|
.sectionHead {
|
|
display: grid;
|
|
grid-template-columns: 1fr auto;
|
|
align-items: baseline;
|
|
column-gap: 1.5rem;
|
|
margin-bottom: var(--section-head-gap);
|
|
}
|
|
|
|
.sectionKicker {
|
|
grid-column: 1 / -1;
|
|
font-size: var(--step--2);
|
|
font-weight: 700;
|
|
letter-spacing: 0.1em;
|
|
text-transform: uppercase;
|
|
color: var(--brand-strong);
|
|
margin: 0 0 0.35rem;
|
|
}
|
|
|
|
.sectionHeading {
|
|
font-family: var(--font-display);
|
|
font-size: var(--step-3);
|
|
font-weight: 700;
|
|
line-height: 1.2;
|
|
letter-spacing: -0.02em;
|
|
color: var(--text-primary);
|
|
margin: 0;
|
|
max-width: 24ch;
|
|
text-wrap: balance;
|
|
}
|
|
|
|
.sectionAside {
|
|
font-size: var(--step--1);
|
|
color: var(--text-muted);
|
|
margin: 0;
|
|
text-align: right;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.sectionHead {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
.sectionHeading {
|
|
font-size: var(--step-2);
|
|
max-width: none;
|
|
}
|
|
.sectionAside {
|
|
text-align: left;
|
|
margin-top: 0.4rem;
|
|
}
|
|
}
|
|
|
|
/* ── Hero ──────────────────────────────────────────────────────────────────
|
|
The artwork fills the panel and the proposition sits on top of it, in the
|
|
empty cream area the illustration reserves on its left. It is not a
|
|
two-column grid: splitting the panel would crop that reserved area off and
|
|
shrink the scene into a thumbnail, which is the one thing the artwork is
|
|
composed not to be.
|
|
|
|
Below the one-column breakpoint the artwork leaves the background and
|
|
becomes a band under the search — see the 860px block. */
|
|
.heroPanel {
|
|
position: relative;
|
|
isolation: isolate;
|
|
background: var(--hero-ground);
|
|
border-radius: var(--radius-xl);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.heroContent {
|
|
position: relative;
|
|
z-index: 2;
|
|
padding: 3.5rem 1.5rem 3.5rem 3rem;
|
|
/* Held inside the artwork's cream area. Beyond roughly this width the text
|
|
runs onto the hillside, where the scrim below is doing all the work. */
|
|
max-width: min(56%, 40rem);
|
|
min-width: 0;
|
|
}
|
|
|
|
.heroArt {
|
|
position: absolute;
|
|
inset: 0;
|
|
z-index: 0;
|
|
}
|
|
|
|
.heroArt picture,
|
|
.heroArt img {
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.heroArt img {
|
|
object-fit: cover;
|
|
/* Centre keeps the schoolhouse in frame at every desktop panel ratio: the
|
|
panel runs about 2.1:1 to 2.7:1 against a 1.78:1 image, so cover crops
|
|
height only, and the school sits well inside the surviving band. */
|
|
object-position: center;
|
|
}
|
|
|
|
/*
|
|
* Contrast guarantee.
|
|
*
|
|
* The artwork's cream area is generous but it is a fixed image on a fluid
|
|
* panel — as the panel widens the text block and the cream drift apart, and at
|
|
* some width the headline would sit on hillside green. Rather than tune the
|
|
* text width per breakpoint and hope, the panel fades its own ground across
|
|
* the left of the artwork, so the copy always has a known surface under it.
|
|
*
|
|
* The gradient starts from --hero-ground, which is sampled from the artwork
|
|
* itself, so in the light theme this is invisible — it reads as the
|
|
* illustration, not as an overlay on it.
|
|
*/
|
|
.heroPanel::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
z-index: 1;
|
|
pointer-events: none;
|
|
background: linear-gradient(
|
|
to right,
|
|
var(--hero-ground) 0%,
|
|
rgba(var(--hero-ground-rgb), 0.97) 26%,
|
|
rgba(var(--hero-ground-rgb), 0.72) 44%,
|
|
rgba(var(--hero-ground-rgb), 0) 62%
|
|
);
|
|
}
|
|
|
|
/*
|
|
* Dark theme.
|
|
*
|
|
* The artwork is a fixed raster, so unlike the drawn hero it cannot be
|
|
* re-graded token by token — but the failure it would otherwise reproduce is
|
|
* the same one: a bright illustration is the brightest object on a near-black
|
|
* page and out-shouts the H1 and the search box. Dimming it in CSS is the
|
|
* whole treatment, and the scrim then fades it into the dark panel instead of
|
|
* into cream.
|
|
*
|
|
* Raising the brightness raises the background under light dark-theme text, so
|
|
* this value has a contrast floor, not just a taste range. Measured off
|
|
* rendered pixels, sampling background up to 120px past each line:
|
|
*
|
|
* brightness title body
|
|
* 0.52 11.01:1 6.44:1
|
|
* 0.75 9.48:1 5.21:1 ← current
|
|
*
|
|
* Body text is the binding one. Going much above 0.75 walks it toward the
|
|
* 4.5:1 floor, and at that point the scrim needs to carry further right rather
|
|
* than the artwork being dimmed less.
|
|
*/
|
|
@media (prefers-color-scheme: dark) {
|
|
.heroArt img {
|
|
filter: brightness(0.75) saturate(0.72) contrast(1.02);
|
|
}
|
|
.heroPanel::before {
|
|
background: linear-gradient(
|
|
to right,
|
|
var(--hero-ground) 0%,
|
|
rgba(var(--hero-ground-rgb), 0.96) 30%,
|
|
rgba(var(--hero-ground-rgb), 0.66) 50%,
|
|
rgba(var(--hero-ground-rgb), 0.15) 78%
|
|
);
|
|
}
|
|
}
|
|
|
|
.heroEyebrow {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
font-size: 0.72rem;
|
|
font-weight: 600;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
/* --brand on its own 10% tint over Sand measures 4.19:1 — the tint darkens
|
|
the ground under the label, and the token was specced against the ground,
|
|
not against the tint. --brand-strong on the same composite is 5.85:1. */
|
|
color: var(--brand-strong);
|
|
background: rgba(var(--brand-rgb), 0.1);
|
|
padding: 0.3rem 0.7rem;
|
|
border-radius: 999px;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.heroEyebrowDot {
|
|
display: inline-block;
|
|
width: 6px;
|
|
height: 6px;
|
|
border-radius: 50%;
|
|
background: var(--brand);
|
|
}
|
|
|
|
.heroTitle {
|
|
font-size: var(--step-4);
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
margin-bottom: 0.75rem;
|
|
line-height: 1.12;
|
|
letter-spacing: -0.03em;
|
|
font-family: var(--font-display);
|
|
max-width: 18ch;
|
|
}
|
|
|
|
/* The em is a colour change, not an emphasis change — Manrope has no italic
|
|
in the loaded weights, so leaving font-style alone would synthesise a slant. */
|
|
.heroEmph {
|
|
color: var(--brand);
|
|
font-style: normal;
|
|
}
|
|
|
|
.heroDescription {
|
|
font-size: var(--step-1);
|
|
color: var(--text-secondary);
|
|
margin: 0 0 1.5rem;
|
|
max-width: 46ch;
|
|
line-height: 1.55;
|
|
}
|
|
|
|
.heroDescription strong {
|
|
color: var(--text-primary);
|
|
font-weight: 600;
|
|
}
|
|
|
|
@media (max-width: 1024px) {
|
|
.heroContent {
|
|
padding: 2.75rem 1.5rem 2.75rem 2rem;
|
|
max-width: min(64%, 34rem);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* One column: the artwork stops being a background and becomes a band under
|
|
* the search.
|
|
*
|
|
* Overlaying at this width does not work — the panel is too narrow for the
|
|
* copy to stay inside the artwork's cream area, so the scrim would have to
|
|
* cover nearly the whole image, which is just a tinted rectangle with a
|
|
* picture faintly behind it. Below the band, <picture> has already switched
|
|
* to the pre-cropped file (see Illustration.tsx); this is the layout half of
|
|
* the same decision.
|
|
*/
|
|
@media (max-width: 860px) {
|
|
/*
|
|
* Artwork above the copy, not below it.
|
|
*
|
|
* The DOM keeps .heroContent first so the desktop overlay does not depend on
|
|
* source order, which left the band stranded under the search on phones —
|
|
* reading as a strip stuck on the end rather than as a hero image. `order`
|
|
* moves it visually only; it is decorative and aria-hidden, so there is no
|
|
* reading order to disturb.
|
|
*
|
|
* It costs about 136px above the search. Measured on a 667px viewport (the
|
|
* shortest phone still in use) the search still lands near 400px, well
|
|
* inside the fold.
|
|
*/
|
|
.heroPanel {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.heroContent {
|
|
padding: 1.75rem 1.5rem 1.75rem;
|
|
max-width: none;
|
|
}
|
|
.heroArt {
|
|
position: static;
|
|
order: -1;
|
|
height: 11rem;
|
|
}
|
|
/* The band crop centres the schoolhouse at about 65% across, so squeezing
|
|
toward 4.9:1 crops the empty sides rather than the subject. */
|
|
.heroArt img {
|
|
object-position: 65% center;
|
|
}
|
|
/* The scrim exists to protect text sitting on the artwork. Nothing sits on
|
|
it here, and leaving it would wash the band out. */
|
|
.heroPanel::before {
|
|
display: none;
|
|
}
|
|
.heroTitle {
|
|
font-size: var(--step-3);
|
|
max-width: none;
|
|
}
|
|
.heroDescription {
|
|
font-size: var(--step-0);
|
|
max-width: none;
|
|
}
|
|
}
|
|
|
|
/* Above the fold on phones, every line costs. Drop the eyebrow tag and the
|
|
long coverage sentence, leaving the proposition and the search — the data
|
|
provenance is carried by the "Official & trusted" prop immediately below
|
|
the hero (audit P1.7, feeds the 46% home exit rate). */
|
|
@media (max-width: 640px) {
|
|
.heroPanel {
|
|
border-radius: var(--radius-lg);
|
|
}
|
|
.heroContent {
|
|
padding: 1.25rem 1.1rem 1.5rem;
|
|
}
|
|
.heroEyebrow,
|
|
.heroDescriptionFull {
|
|
display: none;
|
|
}
|
|
.heroDescription {
|
|
font-size: 0.9rem;
|
|
line-height: 1.45;
|
|
margin-bottom: 1rem;
|
|
}
|
|
.heroTitle {
|
|
font-size: 1.75rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
.heroArt {
|
|
height: 8.5rem;
|
|
}
|
|
}
|
|
|
|
/* ── Value props ───────────────────────────────────────────────────────────
|
|
Four reasons, straight from the guideline. The fourth is ours rather than
|
|
theirs — see the comment on VALUE_PROPS in HomeView.tsx. */
|
|
.valueProps {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: 1.25rem;
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0;
|
|
}
|
|
|
|
.valueProp {
|
|
display: flex;
|
|
gap: 0.8rem;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.propIcon {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex: 0 0 2.6rem;
|
|
width: 2.6rem;
|
|
height: 2.6rem;
|
|
border-radius: var(--radius-md);
|
|
}
|
|
|
|
.propIconTrust {
|
|
background: var(--brand-bg);
|
|
color: var(--brand);
|
|
}
|
|
.propIconCompare {
|
|
background: rgba(var(--sage-rgb), 0.42);
|
|
color: var(--status-above);
|
|
}
|
|
.propIconContext {
|
|
background: rgba(242, 201, 76, 0.26);
|
|
color: var(--mustard-ink);
|
|
}
|
|
.propIconDeadline {
|
|
background: rgba(221, 216, 245, 0.6);
|
|
color: var(--phase-nursery);
|
|
}
|
|
|
|
.propTitle {
|
|
font-family: var(--font-display);
|
|
font-size: var(--step-0);
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
margin: 0 0 0.15rem;
|
|
letter-spacing: -0.01em;
|
|
}
|
|
|
|
.propBody {
|
|
font-size: var(--step--1);
|
|
color: var(--text-muted);
|
|
line-height: 1.5;
|
|
margin: 0;
|
|
}
|
|
|
|
@media (max-width: 900px) {
|
|
.valueProps {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 1rem 1.25rem;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 560px) {
|
|
.valueProps {
|
|
grid-template-columns: 1fr;
|
|
gap: 0.85rem;
|
|
}
|
|
}
|
|
|
|
/* View Toggle */
|
|
.viewToggle {
|
|
display: flex;
|
|
gap: 0.2rem;
|
|
background: var(--bg-secondary);
|
|
padding: 0.2rem;
|
|
border-radius: 6px;
|
|
}
|
|
|
|
.viewToggleBtn {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
padding: 0.375rem 0.625rem;
|
|
font-size: 0.8125rem;
|
|
font-weight: 500;
|
|
background: transparent;
|
|
border: none;
|
|
border-radius: 5px;
|
|
cursor: pointer;
|
|
color: var(--text-secondary);
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.viewToggleBtn:hover {
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.viewToggleBtn.active {
|
|
background: var(--bg-card);
|
|
color: var(--brand-strong);
|
|
box-shadow: 0 2px 4px rgba(var(--shadow-rgb), 0.08);
|
|
}
|
|
|
|
.viewToggleBtn svg {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.results {
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.mapViewResults {
|
|
margin-top: 0;
|
|
}
|
|
|
|
/* Map View Layout */
|
|
.mapViewContainer {
|
|
display: grid;
|
|
grid-template-columns: 1fr 340px;
|
|
gap: 1rem;
|
|
height: calc(100dvh - 280px);
|
|
min-height: 520px;
|
|
max-height: 800px;
|
|
}
|
|
|
|
.mapContainer {
|
|
border-radius: 10px;
|
|
overflow: hidden;
|
|
border: 1px solid var(--border);
|
|
height: 100%;
|
|
}
|
|
|
|
.compactList {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
overflow-y: auto;
|
|
height: 100%;
|
|
padding-right: 0.375rem;
|
|
}
|
|
|
|
.compactList::-webkit-scrollbar {
|
|
width: 6px;
|
|
}
|
|
|
|
.compactList::-webkit-scrollbar-track {
|
|
background: var(--bg-secondary);
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.compactList::-webkit-scrollbar-thumb {
|
|
background: var(--border);
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.compactList::-webkit-scrollbar-thumb:hover {
|
|
background: var(--text-muted);
|
|
}
|
|
|
|
/* Compact School Item */
|
|
.compactItem {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
padding: 0.625rem 0.75rem;
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.compactItem:hover {
|
|
border-color: var(--brand);
|
|
box-shadow: 0 2px 6px rgba(var(--shadow-rgb), 0.05);
|
|
}
|
|
|
|
.compactItemContent {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.compactItemHeader {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.375rem;
|
|
margin-bottom: 0.125rem;
|
|
}
|
|
|
|
.compactItemName {
|
|
font-weight: 600;
|
|
font-size: 0.8125rem;
|
|
color: var(--text-primary);
|
|
text-decoration: none;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.compactItemName:hover {
|
|
color: var(--brand-strong);
|
|
}
|
|
|
|
.distanceBadge {
|
|
flex-shrink: 0;
|
|
padding: 0.0625rem 0.375rem;
|
|
font-size: 0.6875rem;
|
|
font-weight: 600;
|
|
background: var(--brand);
|
|
color: var(--brand-on);
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.compactItemMeta {
|
|
display: flex;
|
|
gap: 0.375rem;
|
|
font-size: 0.6875rem;
|
|
color: var(--text-secondary);
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.compactItemMeta span:not(:last-child)::after {
|
|
content: '·';
|
|
margin-left: 0.375rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.compactItemStats {
|
|
display: flex;
|
|
gap: 0.75rem;
|
|
font-size: 0.6875rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.compactStat strong {
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.compactItemActions {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.25rem;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
|
|
.sectionHeader {
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.sectionHeader h2 {
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
margin-bottom: 0.5rem;
|
|
color: var(--text-primary);
|
|
font-family: var(--font-display);
|
|
}
|
|
|
|
/* Decorative coral bar under section headings */
|
|
.sectionHeader h2::after {
|
|
content: '';
|
|
display: block;
|
|
width: 50px;
|
|
height: 2px;
|
|
background: var(--brand);
|
|
border-radius: 1px;
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
.sectionDescription {
|
|
font-size: 0.9375rem;
|
|
color: var(--text-secondary);
|
|
margin: 0;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.schoolList {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
margin-bottom: 1.25rem;
|
|
}
|
|
|
|
/* Staggered fade-in for rows */
|
|
.schoolList > *:nth-child(1) { animation-delay: 0ms; }
|
|
.schoolList > *:nth-child(2) { animation-delay: 30ms; }
|
|
.schoolList > *:nth-child(3) { animation-delay: 60ms; }
|
|
.schoolList > *:nth-child(4) { animation-delay: 90ms; }
|
|
.schoolList > *:nth-child(5) { animation-delay: 120ms; }
|
|
.schoolList > *:nth-child(6) { animation-delay: 150ms; }
|
|
.schoolList > *:nth-child(7) { animation-delay: 180ms; }
|
|
.schoolList > *:nth-child(8) { animation-delay: 210ms; }
|
|
.schoolList > *:nth-child(9) { animation-delay: 240ms; }
|
|
.schoolList > *:nth-child(n+10) { animation-delay: 270ms; }
|
|
|
|
.emptyState {
|
|
text-align: center;
|
|
padding: 2.5rem 1.5rem;
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
}
|
|
|
|
.emptyStateTitle {
|
|
font-size: 1.25rem;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
margin-bottom: 0.375rem;
|
|
font-family: var(--font-display);
|
|
}
|
|
|
|
.emptyStateDescription {
|
|
font-size: 0.9375rem;
|
|
color: var(--text-secondary);
|
|
max-width: 380px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.resultsHeader {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.resultsHeaderActions {
|
|
width: 100%;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.viewToggle {
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* The sort <select> sizes to its widest option ("Highest Reading, Writing
|
|
& Maths %"), which overflows a phone viewport — beside the view toggle it
|
|
ran off the right edge. Let it flex into the remaining space and shrink;
|
|
the selected label truncates instead of pushing past the screen. */
|
|
.sortSelect {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.mapViewContainer {
|
|
grid-template-columns: 1fr;
|
|
grid-template-rows: 260px auto;
|
|
height: auto;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.mapContainer {
|
|
height: 260px;
|
|
}
|
|
|
|
.compactList {
|
|
height: auto;
|
|
max-height: 350px;
|
|
padding-right: 0;
|
|
}
|
|
|
|
.compactItem {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.compactItemActions {
|
|
flex-direction: row;
|
|
}
|
|
|
|
.compactItemActions > * {
|
|
flex: 1;
|
|
}
|
|
|
|
.emptyState {
|
|
padding: 2rem 1.25rem;
|
|
}
|
|
}
|
|
|
|
/* Highlighted List Item */
|
|
.highlightedItem .compactItem {
|
|
border-color: var(--brand);
|
|
box-shadow: 0 0 0 1px var(--brand);
|
|
background: var(--bg-secondary);
|
|
}
|
|
|
|
/* Mobile Bottom Sheet */
|
|
.bottomSheetWrapper {
|
|
display: none;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.bottomSheetWrapper {
|
|
display: block;
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
z-index: 1000;
|
|
padding: 1rem;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.bottomSheet {
|
|
position: relative;
|
|
background: var(--bg-card);
|
|
border-radius: 12px;
|
|
box-shadow: 0 -4px 24px rgba(var(--shadow-rgb), 0.15);
|
|
pointer-events: auto;
|
|
animation: slideUpSheet 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
}
|
|
|
|
.bottomSheet .compactItem {
|
|
border: none;
|
|
box-shadow: none;
|
|
background: transparent;
|
|
padding: 1rem;
|
|
}
|
|
|
|
.bottomSheet .compactItem:hover {
|
|
box-shadow: none;
|
|
}
|
|
|
|
.closeSheetBtn {
|
|
position: absolute;
|
|
top: -12px;
|
|
right: -12px;
|
|
width: 30px;
|
|
height: 30px;
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 1.25rem;
|
|
color: var(--text-secondary);
|
|
cursor: pointer;
|
|
box-shadow: 0 2px 8px rgba(var(--shadow-rgb), 0.1);
|
|
z-index: 10;
|
|
}
|
|
|
|
@keyframes slideUpSheet {
|
|
from {
|
|
transform: translateY(120%);
|
|
}
|
|
to {
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
/* When map view on mobile, expand map and hide list */
|
|
.mapViewContainer {
|
|
grid-template-columns: 1fr;
|
|
grid-template-rows: 1fr;
|
|
height: calc(100dvh - 280px);
|
|
min-height: 400px;
|
|
}
|
|
|
|
.mapContainer {
|
|
height: 100%;
|
|
}
|
|
|
|
.compactList {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
.quickSearches {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.5rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.quickSearchLabel {
|
|
font-size: 0.875rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.quickSearchChip {
|
|
padding: 0.375rem 0.875rem;
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
border-radius: 999px;
|
|
font-size: 0.875rem;
|
|
color: var(--text-secondary);
|
|
text-decoration: none;
|
|
transition: all var(--transition);
|
|
}
|
|
|
|
.quickSearchChip:hover {
|
|
background: var(--brand-strong);
|
|
color: var(--brand-on);
|
|
border-color: var(--brand-strong);
|
|
}
|
|
|
|
/* ── How it works section ─────────────────────────────────────────────────
|
|
A Sand band, borrowing the hero panel's ground and radius so the page has
|
|
two anchored bands rather than one designed element and a long tail of
|
|
unstyled strips. */
|
|
|
|
.howItWorks {
|
|
background: var(--bg-secondary);
|
|
border-radius: var(--radius-xl);
|
|
padding: 2.5rem 2rem;
|
|
}
|
|
|
|
.hiwGrid {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 1rem;
|
|
}
|
|
|
|
.hiwCard {
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
border-radius: 14px;
|
|
padding: 1.25rem;
|
|
box-shadow: 0 2px 8px rgba(var(--shadow-rgb), 0.06);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.9rem;
|
|
}
|
|
|
|
/*
|
|
* The preview panel sits on the card surface, not on Sand.
|
|
*
|
|
* Everything inside it is a translucent status or brand tint, and those tokens
|
|
* are specced to clear AA over --bg-card. Over Sand the same tints composite
|
|
* darker, which put the report-card chips at 4.22:1 and 4.29:1 against a 4.5
|
|
* floor — the ratio depended on a ground two levels up. On the card surface
|
|
* they are 5.69:1 and 5.81:1. The border keeps it reading as an inset frame
|
|
* now that panel and card share a colour.
|
|
*/
|
|
.hiwVisual {
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
padding: 0.9rem;
|
|
min-height: 180px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.hiwPhaseBlock {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.35rem;
|
|
}
|
|
|
|
.hiwPhaseLabel {
|
|
font-size: 0.58rem;
|
|
font-weight: 600;
|
|
letter-spacing: 0.1em;
|
|
text-transform: uppercase;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.hiwPhaseLabel strong {
|
|
color: var(--brand);
|
|
font-weight: 700;
|
|
}
|
|
|
|
.hiwCardBody {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.3rem;
|
|
}
|
|
|
|
.hiwStep {
|
|
font-size: 0.65rem;
|
|
font-weight: 700;
|
|
letter-spacing: 0.1em;
|
|
text-transform: uppercase;
|
|
color: var(--brand-strong);
|
|
}
|
|
|
|
.hiwTitle {
|
|
font-family: var(--font-display);
|
|
font-size: 1.1rem;
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
line-height: 1.25;
|
|
}
|
|
|
|
.hiwDesc {
|
|
font-size: 0.86rem;
|
|
color: var(--text-secondary);
|
|
line-height: 1.45;
|
|
margin: 0;
|
|
}
|
|
|
|
/* Mini cascade (performance card) */
|
|
.miniCascade {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 0.4rem;
|
|
}
|
|
|
|
.miniCascadeCol {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.15rem;
|
|
min-width: 0;
|
|
}
|
|
|
|
.miniSubj {
|
|
font-size: 0.5rem;
|
|
font-weight: 700;
|
|
letter-spacing: 0.1em;
|
|
text-transform: uppercase;
|
|
color: var(--text-muted);
|
|
margin-bottom: 0.1rem;
|
|
}
|
|
|
|
.miniRowHead {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: baseline;
|
|
gap: 0.2rem;
|
|
font-size: 0.44rem;
|
|
color: var(--text-muted);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.01em;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.miniRowHead strong {
|
|
font-family: var(--font-display);
|
|
font-size: 0.62rem;
|
|
color: var(--text-primary);
|
|
font-weight: 700;
|
|
letter-spacing: 0;
|
|
text-transform: none;
|
|
}
|
|
|
|
.miniTrack {
|
|
height: 5px;
|
|
border-radius: 2px;
|
|
background: rgba(var(--status-above-rgb), 0.08);
|
|
position: relative;
|
|
overflow: visible;
|
|
}
|
|
|
|
.miniBarExp {
|
|
height: 100%;
|
|
border-radius: 2px;
|
|
background: var(--brand);
|
|
}
|
|
|
|
.miniBarExc {
|
|
height: 100%;
|
|
border-radius: 2px;
|
|
background: var(--brand);
|
|
}
|
|
|
|
.miniNatPill {
|
|
position: absolute;
|
|
top: -9px;
|
|
transform: translateX(-50%);
|
|
background: var(--brand);
|
|
color: var(--text-inverse);
|
|
font-size: 0.4rem;
|
|
font-weight: 700;
|
|
padding: 0.05rem 0.2rem;
|
|
border-radius: 3px;
|
|
z-index: 2;
|
|
white-space: nowrap;
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* Attainment 8 mini bar */
|
|
.att8Row {
|
|
display: grid;
|
|
grid-template-columns: 1fr auto;
|
|
gap: 0.6rem;
|
|
align-items: center;
|
|
}
|
|
|
|
.att8BarWrap {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.2rem;
|
|
}
|
|
|
|
.att8BarHead {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
font-size: 0.5rem;
|
|
color: var(--text-muted);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
.att8Track {
|
|
height: 7px;
|
|
background: rgba(var(--status-above-rgb), 0.08);
|
|
border-radius: 3px;
|
|
position: relative;
|
|
}
|
|
|
|
.att8Fill {
|
|
height: 100%;
|
|
background: var(--brand);
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.att8NatLine {
|
|
position: absolute;
|
|
top: -2px;
|
|
bottom: -2px;
|
|
width: 1.5px;
|
|
background: rgba(var(--brand-rgb), 0.6);
|
|
z-index: 2;
|
|
}
|
|
|
|
.att8Score {
|
|
font-variant-numeric: tabular-nums;
|
|
text-align: right;
|
|
}
|
|
|
|
.att8Value {
|
|
font-variant-numeric: tabular-nums;
|
|
font-family: var(--font-display);
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
color: var(--status-above);
|
|
line-height: 1;
|
|
}
|
|
|
|
.att8Delta {
|
|
font-variant-numeric: tabular-nums;
|
|
font-size: 0.55rem;
|
|
color: var(--status-above);
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
margin-top: 0.15rem;
|
|
}
|
|
|
|
/* Ofsted preview */
|
|
.ofstedPreview {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
width: 100%;
|
|
padding: 0.4rem 0.2rem;
|
|
}
|
|
|
|
.ofstedHead {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding-bottom: 0.4rem;
|
|
border-bottom: 1.5px solid var(--border);
|
|
}
|
|
|
|
.ofstedBullet {
|
|
display: block;
|
|
width: 3px;
|
|
height: 1em;
|
|
background: var(--brand);
|
|
border-radius: 2px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.ofstedTitle {
|
|
font-family: var(--font-display);
|
|
font-size: 0.85rem;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.ofstedFramework {
|
|
align-self: flex-start;
|
|
padding: 0.15rem 0.5rem;
|
|
border-radius: 4px;
|
|
background: rgba(var(--phase-secondary-rgb), 0.12);
|
|
color: var(--phase-secondary-text);
|
|
font-size: 0.5rem;
|
|
font-weight: 700;
|
|
letter-spacing: 0.05em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.rcList {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.3rem;
|
|
}
|
|
|
|
.rcRow {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.rcArea {
|
|
font-size: 0.62rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.rcChip {
|
|
padding: 0.1rem 0.4rem;
|
|
border-radius: 3px;
|
|
font-size: 0.52rem;
|
|
font-weight: 700;
|
|
letter-spacing: 0.03em;
|
|
white-space: nowrap;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.rcChipGood {
|
|
background: rgba(var(--status-above-rgb), 0.12);
|
|
color: var(--status-above);
|
|
}
|
|
|
|
.rcChipNeutral {
|
|
background: var(--status-below-bg);
|
|
color: var(--status-below);
|
|
}
|
|
|
|
/* Safeguarding is a separate binary met/not-met judgement, not a graded area,
|
|
so it sits below the five-point list behind a divider with its own chip. */
|
|
.rcSafeguarding {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 0.5rem;
|
|
margin-top: 0.05rem;
|
|
padding-top: 0.35rem;
|
|
border-top: 1px dashed var(--border);
|
|
}
|
|
|
|
.rcSep {
|
|
color: var(--text-muted);
|
|
font-style: italic;
|
|
}
|
|
|
|
.rcChipMet {
|
|
background: transparent;
|
|
color: var(--status-above);
|
|
border: 1px solid rgba(var(--status-above-rgb), 0.4);
|
|
}
|
|
|
|
.ofstedMeta {
|
|
font-size: 0.6rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
/* Compare preview */
|
|
.comparePreview {
|
|
width: 100%;
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
font-size: 0.6rem;
|
|
}
|
|
|
|
.compareHead {
|
|
display: grid;
|
|
grid-template-columns: 1fr repeat(2, 1fr);
|
|
background: rgba(var(--status-above-rgb), 0.1);
|
|
padding: 0.35rem 0.5rem;
|
|
gap: 0.35rem;
|
|
}
|
|
|
|
.compareHeadCell {
|
|
font-family: var(--font-display);
|
|
font-size: 0.62rem;
|
|
font-weight: 700;
|
|
color: var(--status-above);
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.compareHeadLabel {
|
|
font-family: inherit;
|
|
font-size: 0.48rem;
|
|
/* --text-muted on the header row's tint is 4.45:1 in the dark theme —
|
|
under the floor by a hair, because the tint darkens the ground beneath
|
|
the label. --text-secondary clears it on both grounds. */
|
|
color: var(--text-secondary);
|
|
font-weight: 500;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.compareRow {
|
|
display: grid;
|
|
grid-template-columns: 1fr repeat(2, 1fr);
|
|
padding: 0.3rem 0.5rem;
|
|
gap: 0.35rem;
|
|
border-top: 1px solid var(--border);
|
|
align-items: baseline;
|
|
}
|
|
|
|
.compareRowLabel {
|
|
font-size: 0.55rem;
|
|
color: var(--text-muted);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
.compareRowVal {
|
|
font-family: var(--font-display);
|
|
font-size: 0.72rem;
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.compareRowValHi {
|
|
color: var(--status-above);
|
|
}
|
|
|
|
.compareFoot {
|
|
font-size: 0.52rem;
|
|
color: var(--text-muted);
|
|
padding: 0.35rem 0.5rem;
|
|
background: var(--bg-secondary);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
text-align: center;
|
|
border-top: 1px solid var(--border);
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.hiwGrid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
.howItWorks {
|
|
padding: 1.75rem 1.25rem;
|
|
border-radius: var(--radius-lg);
|
|
}
|
|
}
|
|
|
|
/* On phones, hide the scaled-down preview visuals (text becomes illegible
|
|
at this size) and let the explanatory text carry each card. */
|
|
@media (max-width: 640px) {
|
|
.hiwVisual {
|
|
display: none;
|
|
}
|
|
.hiwCard {
|
|
padding: 1rem 1.1rem;
|
|
gap: 0.45rem;
|
|
}
|
|
.hiwTitle {
|
|
font-size: 1.05rem;
|
|
}
|
|
.hiwDesc {
|
|
font-size: 0.9rem;
|
|
}
|
|
}
|
|
|
|
/* ── Editorial section ───────────────────────────────── */
|
|
|
|
/* Heading beside the prose rather than above it. The fact box used to occupy
|
|
the right-hand column; with it gone, a single column left the card half
|
|
empty, because the prose is capped at a 62ch reading measure and the card
|
|
is not. */
|
|
.editorialCard {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr);
|
|
column-gap: 3rem;
|
|
padding: 2rem 2.25rem;
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
}
|
|
|
|
/* The shared header pattern spaces itself for a header sitting above its
|
|
content; here it sits beside it. */
|
|
.editorialCard .sectionHead {
|
|
margin-bottom: 0;
|
|
display: block;
|
|
}
|
|
|
|
.editorialCard .coverageLine {
|
|
grid-column: 1 / -1;
|
|
}
|
|
|
|
.editorialText {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.65rem;
|
|
}
|
|
|
|
/* Running prose is the one place the serif appears. Slightly larger than the
|
|
UI around it because Literata's x-height sits lower than the grotesque's. */
|
|
.editorialText p {
|
|
font-family: var(--font-prose);
|
|
font-size: 0.97rem;
|
|
color: var(--text-secondary);
|
|
line-height: 1.65;
|
|
margin: 0;
|
|
max-width: 62ch;
|
|
}
|
|
|
|
/* One line of live coverage figures, replacing a six-row table whose headline
|
|
number was its own hardcoded fallback. Quiet by design: it is provenance,
|
|
not a claim. */
|
|
.coverageLine {
|
|
margin: 1.25rem 0 0;
|
|
padding-top: 1rem;
|
|
border-top: 1px solid var(--border);
|
|
font-size: var(--step--1);
|
|
font-variant-numeric: tabular-nums;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.editorialCard {
|
|
grid-template-columns: 1fr;
|
|
padding: 1.25rem;
|
|
}
|
|
.editorialCard .sectionHead {
|
|
margin-bottom: var(--section-head-gap);
|
|
}
|
|
}
|
|
|
|
.resultsHeader {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
flex-wrap: wrap;
|
|
gap: 0.75rem;
|
|
padding: 0 0 1rem;
|
|
}
|
|
|
|
.resultsHeaderActions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.sortSelect {
|
|
padding: 0.375rem 0.75rem;
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-md);
|
|
background: var(--bg-card);
|
|
color: var(--text-secondary);
|
|
font-size: 0.875rem;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.activeFilters {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.filterChip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.375rem;
|
|
padding: 0.25rem 0.625rem;
|
|
background: var(--bg-secondary);
|
|
border: 1px solid var(--border);
|
|
border-radius: 999px;
|
|
font-size: 0.8rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.chipRemove {
|
|
color: var(--text-muted);
|
|
text-decoration: none;
|
|
font-size: 0.9rem;
|
|
line-height: 1;
|
|
transition: color var(--transition, 0.2s ease);
|
|
}
|
|
|
|
.chipRemove:hover {
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.loadMoreSection {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
padding: 1.5rem 0;
|
|
}
|
|
|
|
.loadMoreCount {
|
|
font-variant-numeric: tabular-nums;
|
|
font-size: 0.875rem;
|
|
color: var(--text-muted);
|
|
margin: 0;
|
|
}
|
|
|
|
.loadMoreButton {
|
|
min-width: 160px;
|
|
}
|
|
/* =========================================================
|
|
Next admissions deadline
|
|
|
|
One bar, replacing four equally-weighted countdown cards. Those gave the
|
|
page's largest numeral to things up to 245 days away, and two of the four
|
|
counted down to offer days — dates you receive something on, which cannot
|
|
be missed. The headline is the next real deadline; the rest stay on the
|
|
page as one supporting line, so no fact was lost, only its weight.
|
|
========================================================= */
|
|
|
|
.admissions {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.85rem;
|
|
}
|
|
|
|
.nextDeadline {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 1.5rem;
|
|
position: relative;
|
|
overflow: hidden;
|
|
padding: 1.15rem 1.5rem 1.15rem 1.75rem;
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow-soft);
|
|
color: inherit;
|
|
text-decoration: none;
|
|
transition: border-color var(--transition), box-shadow var(--transition);
|
|
}
|
|
|
|
/* A brand rule down the leading edge — the same device the countdown cards
|
|
used along their top edge, kept so the band still reads as admissions. */
|
|
.nextDeadline::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0 auto 0 0;
|
|
width: 5px;
|
|
background: var(--brand);
|
|
}
|
|
|
|
.nextDeadline:hover {
|
|
border-color: var(--border-strong);
|
|
box-shadow: var(--shadow-medium);
|
|
}
|
|
|
|
.nextDeadlineBody {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.15rem;
|
|
min-width: 0;
|
|
}
|
|
|
|
.nextDeadlineKicker {
|
|
font-size: var(--step--2);
|
|
font-weight: 700;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
color: var(--brand-strong);
|
|
}
|
|
|
|
.nextDeadlineTitle {
|
|
font-family: var(--font-display);
|
|
font-size: var(--step-1);
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
line-height: 1.25;
|
|
}
|
|
|
|
.nextDeadlineDate {
|
|
font-size: var(--step--1);
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.nextDeadlineCount {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 0.35rem;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.nextDeadlineDays {
|
|
font-family: var(--font-display);
|
|
font-size: var(--step-3);
|
|
font-weight: 700;
|
|
line-height: 1;
|
|
letter-spacing: -0.02em;
|
|
font-variant-numeric: tabular-nums;
|
|
color: var(--brand-strong);
|
|
}
|
|
|
|
.nextDeadlineUnit {
|
|
font-size: var(--step--1);
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
/* Inside a fortnight the count changes hue rather than size, so urgency is
|
|
legible without the bar growing. Terracotta is the status hue, not the
|
|
action hue — this is a state, not a button. */
|
|
.nextDeadlineUrgent .nextDeadlineDays {
|
|
color: var(--status-below);
|
|
}
|
|
|
|
.laterDates {
|
|
font-size: var(--step--1);
|
|
color: var(--text-muted);
|
|
line-height: 1.6;
|
|
margin: 0;
|
|
}
|
|
|
|
.laterDatesLink {
|
|
color: var(--brand);
|
|
font-weight: 600;
|
|
text-decoration: none;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.laterDatesLink:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.nextDeadline {
|
|
align-items: flex-start;
|
|
flex-direction: column;
|
|
gap: 0.75rem;
|
|
padding: 1rem 1.1rem 1rem 1.35rem;
|
|
}
|
|
.nextDeadlineCount {
|
|
align-items: baseline;
|
|
}
|
|
}
|