/* The School Record: the whole visual system.
 *
 * Extracted from a <style> block in base.html, where it had grown to 40 kB --
 * 41% of every page, shipped again on each of 17,740 school pages, and enough
 * to push a paginated listing past the 100 kB that tests/api/test_public_pages
 * holds it to. As a file it is fetched once and cached; /static is served with
 * a long max-age, so bump the ?v= in base.html whenever this changes.
 *
 * No build step, no framework, and no Jinja: this file is plain CSS and must
 * stay that way, because the template no longer gets a chance to render it.
 */

/* Fraunces and Inter, self-hosted: loading them from fonts.googleapis.com
   cost first paint two round trips on third-party origins. Why these subsets,
   the OFL licence and how to regenerate: static/fonts/README.txt */
@font-face {
  font-family: 'Fraunces';
  font-style: italic;
  font-weight: 300 700;
  font-display: swap;
  src: url(/static/fonts/fraunces-latin-italic.woff2?v=6) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url(/static/fonts/fraunces-latin.woff2?v=6) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Fraunces';
  font-style: italic;
  font-weight: 300 700;
  font-display: swap;
  src: url(/static/fonts/fraunces-latin-ext-italic.woff2?v=6) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url(/static/fonts/fraunces-latin-ext.woff2?v=6) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url(/static/fonts/inter-latin-ext.woff2?v=5) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url(/static/fonts/inter-latin.woff2?v=5) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ───────────────────────────────────────────────────────────────────────────
   The School Record: the visual system.

   One stylesheet, no build step, no framework. The site publishes numbers
   copied from official sources, so the design has one job: make the table the
   most authoritative thing on the page, and keep everything else out of its
   way.

   Four rules it follows, in the order they matter:

   1. **Two typefaces, no more.** Fraunces for headings, Inter for body, labels
      and every figure. A third face would read as decoration, and this site
      cannot afford to look decorated.
   2. **Numbers are tabular everywhere they can be compared.** Digits line up
      down a column, so a reader sees the shape of a series before reading a
      single value. That is set once on the table element rather than per cell,
      because a column that forgets it is the one that misleads.
   3. **One accent.** #1F4E79 marks links, the focused control, and the single
      data series a figure is about. Nothing else competes for it, which is why
      it still means something when it appears.
   4. **Colour never carries a judgement.** Grades and categories are named in
      words. The reasoning is written out at the top of catchment/api/charts.py
      and the palette here is the same one those figures draw with.

   The spacing scale is 8/12/16/24/32/48/64 and nothing else. A gap that is not
   on it is a mistake rather than a decision.
   ─────────────────────────────────────────────────────────────────────────── */

:root{
  /* Colour.

     A warm ground rather than a cool one, and ink rather than black. The site
     is a reference publication and reads like one on paper; #F8F6F2 is the
     nearest a screen gets to it without going cream enough to look tinted. */
  --ink:#1A1A18;
  --muted:#6E6A63;
  --line:#E0DBD2;
  --paper:#F8F6F2;
  --panel:#FFFFFF;
  --accent:#B34A26;
  --accent-deep:#8F3A1D;
  --accent-wash:#F6EAE4;
  --row-hover:#F3F0EA;

  /* The second accent: the deep petrol that every line on every chart is drawn
     in. Petrol against coral separates by 0.40 under simulated colour
     blindness, where 0.12 is the point two marks start being confused, and both
     clear 4.5:1 on the ground so either can carry a label as well as a mark. */
  --accent-2:#3C5A6E;
  --accent-2-wash:#EAEEF1;

  /* Two palettes, and the split between them is the whole contract.

     SERIES colours are load-bearing. A line drawn in one of these may be the
     only thing distinguishing it from the line beside it, so they are chosen by
     search against simulated deuteranopia, protanopia and tritanopia -- worst
     pair 0.29 against a 0.12 threshold -- and every one clears 3:1 on the
     ground. Three, not six: nothing here needs more, and a longer ramp is an
     invitation to use a colour nobody checked. */
  --series-1:#3C5A6E;
  --series-2:#B34A26;
  --series-3:#A43DA4;
  /* Context, never a series: the line a highlighted line is read against. */
  --data-context:#8F8A82;

  /* FILL colours are not load-bearing, and must never be made so. They sit
     between 1.78:1 and 2.95:1 on the ground, under the 3:1 a mark needs, and
     the softest pair separates at 0.06 for a colour-blind reader -- half the
     threshold. They are usable at all only because every shape filled with one
     also carries an ink outline and its own printed figure, so the colour is
     the last thing identifying it rather than the first.

     If a chart is ever drawn that tells two series apart by fill alone, this
     palette is the wrong tool and --series-* is the right one. */
  --fill-1:#A9BF92;
  --fill-2:#9DBCD6;
  --fill-3:#E9A48C;
  --fill-4:#DCB66E;
  --fill-pale:#E8E4DB;

  /* Section tints, on the warm ground. */
  --tint-hero:#F4F1EA;
  --tint-panel:#F5F2EC;

  /* Spacing. The only values in this file. */
  --s1:8px; --s2:12px; --s3:16px; --s4:24px; --s5:32px; --s6:48px; --s7:64px;

  --serif:"Fraunces","Source Serif 4",Georgia,"Times New Roman",serif;
  --sans:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;

  /* The shell is wide enough for a six-column table; prose inside it is held
     to a measure, because 1,120px of running text is unreadable. */
  --shell:1120px;
  --measure:68ch;
  --gutter:24px;

  --ease:150ms ease-out;
}
@media (min-width:640px){ :root{ --gutter:32px; } }
@media (min-width:1024px){ :root{ --gutter:48px; } }

*{ box-sizing:border-box; }

body{
  margin:0;
  background:var(--paper);
  color:var(--ink);
  font-family:var(--sans);
  font-size:17px;
  line-height:1.65;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}

.wrap{ max-width:var(--shell); margin:0 auto; padding:0 var(--gutter); }
main.wrap{ padding-block:var(--s6) var(--s7); }

/* ── Type ──────────────────────────────────────────────────────────────────
   Heading sizes are clamped rather than stepped at breakpoints: the hero has
   to hold 48-56px on a desktop and still fit "School admissions figures, from
   the source" on a 375px phone without hyphenating. */

h1,h2,h3{ font-family:var(--serif); font-weight:600; color:var(--ink); }

h1{
  font-size:clamp(2rem, 1.5rem + 2vw, 2.5rem);
  line-height:1.15;
  letter-spacing:-0.02em;
  margin:0 0 var(--s3);
}
h2{
  font-size:clamp(1.5rem, 1.25rem + 1vw, 1.875rem);
  line-height:1.2;
  letter-spacing:-0.015em;
  margin:var(--s7) 0 var(--s3);
}
h3{
  font-size:1.3125rem;
  line-height:1.3;
  letter-spacing:-0.01em;
  margin:var(--s5) 0 var(--s2);
}
/* A heading that introduces the paragraph directly under it sits closer to it
   than to whatever came before. */
h2 + p, h3 + p, h1 + p{ margin-top:0; }

p{ margin:0 0 var(--s3); max-width:var(--measure); }
/* Running lists are prose and take the prose measure. Three lists on the site
   are not prose -- the council columns, a map legend, and the stat cards -- and
   they opt out by name here rather than by fighting this rule with specificity
   further down the file. A list that looks squeezed into the middle of a wide
   page is usually one that belongs on this line and is not. */
:is(main.wrap > ul:not(.col-list):not(.legend):not(.stat-grid), main.wrap > ol){
  max-width:var(--measure); padding-left:1.15rem;
}
:is(main.wrap > ul:not(.col-list):not(.legend):not(.stat-grid), main.wrap > ol) li{
  margin:var(--s1) 0;
}

strong{ font-weight:600; }

/* Supporting text, in three weights of quiet. `.sub` sits under a title, 
   `.section-note` explains what a table is about to show, and `.cite` carries
   the source line. */
.sub{ color:var(--muted); font-size:1.0625rem; margin:0 0 var(--s2); max-width:var(--measure); }
.section-note{ color:var(--muted); font-size:.9375rem; line-height:1.55; margin:0 0 var(--s3); max-width:var(--measure); }
.cite{ color:var(--muted); font-size:.8125rem; line-height:1.55; margin:var(--s2) 0 0; max-width:var(--measure); }
.label{ color:var(--muted); }
.none{ color:var(--muted); font-style:italic; }
.fact{ margin:var(--s1) 0; }
.flag{ font-weight:600; }

/* A summary that has to be read before the list underneath it. Used where a
   page lists one figure per year and the run as a whole says something the
   newest row does not: a reader scanning a list takes the top of it as the
   answer, so the reading of the whole run goes above the list, not below.
   Tinted rather than boxed in a heavy border, so it reads as part of the page
   and not as an advert. */
/* A grouped set of statements. Ruled, not filled.
   
   It used to be filled, and on a school page that put eight tinted blocks down
   12,900px -- 26% of the page was panel. Past about a tenth a tint stops saying
   "look here" and becomes a second background, so the page reads as two walls
   instead of one. The rule keeps the grouping and gives the fill back its
   meaning: .alert is now the only filled block on a page, and it is the one
   that should stop a reader.

   The first line is set a size up, so a block of five sentences has somewhere
   to start rather than presenting five equal ones. */
.callout{
  border-left:3px solid var(--accent);
  padding:var(--s1) 0 var(--s1) var(--s4);
  margin:0 0 var(--s4);
  max-width:var(--measure);
}
.callout > h3{ margin:0 0 var(--s1); font-size:1.0625rem; }
.callout > p:first-of-type{ font-size:1.0625rem; color:var(--ink); }
.callout > .fact:last-child, .callout > p:last-child{ margin-bottom:0; }

/* ── Links ─────────────────────────────────────────────────────────────── */

a{ color:var(--accent); text-decoration-thickness:1px; text-underline-offset:2px; transition:color var(--ease); }
/* A link inside a data table is a school name in a column of school names, and
   underlining every one of them turns the column into a fence. The underline
   comes back on hover, which is where it is doing work. */
table a{ text-decoration:none; }
table a:hover{ text-decoration:underline; }
a:hover{ color:var(--accent-deep); }
:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; border-radius:2px; }

/* ── Masthead ──────────────────────────────────────────────────────────── */

header.site{
  background:var(--panel);
  border-top:3px solid var(--accent);
  border-bottom:1px solid var(--line);
}

/* Items divided by hairlines rather than by space alone. It is the device a
   printed masthead uses, it survives a long item next to a short one, and it
   reads as a set rather than as five things that happen to be in a row. */
/* ── The lockup ────────────────────────────────────────────────────────────
   Option 06, built from the site's own tokens rather than an image file. The
   name is set in the same light Fraunces as the hero headline, so the masthead
   and the page opening are visibly the same voice. */

header.site .wrap{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:var(--s5);
  /* The lockup is two stacked parts and needs room around it or it reads as
     stuck to the edges of the bar. At 16px it filled 59% of the masthead's
     height.

     this leaves about as much air above and below as the lockup is tall,
     which is what makes a masthead look set rather than packed. */
  padding-block:var(--s5);
}

/* Sized by its own content. A fixed 430px basis left empty space inside the
   brand box that did nothing, since space-between already pushes the nav right. */
.brand{ display:block; flex:0 1 auto; min-width:0; }

.brand-lockup{ display:block; width:100%; color:var(--ink); text-decoration:none; }
.brand-lockup:hover{ color:var(--accent); }

.brand-name{
  display:block;
  font-family:var(--serif);
  /* Smaller than the artwork's own proportions. At 2.85rem it ran to 41px
     against a 15px nav -- nearly three times the size -- and competed with the
     53px hero headline one element below it. A masthead identifies the site; it
     is not the loudest thing on the page. */
  font-size:clamp(1.85rem, 2.4vw, 2.25rem);
  font-weight:300;
  line-height:.95;
  letter-spacing:-.035em;
  white-space:nowrap;
}

/* A rule and a descriptor, sized off the name. Decorative: the link already
   carries the site's name, and a screen reader reading it twice is noise. */
.brand-meta{
  display:grid;
  grid-template-columns:minmax(72px, 150px) auto;
  align-items:center;
  gap:var(--s2);
  margin-top:var(--s1);
}
.brand-rule{ height:1.5px; background:currentColor; }
.brand-tagline{
  font-family:var(--sans);
  font-size:.625rem;
  font-weight:600;
  line-height:1;
  letter-spacing:.18em;
  text-transform:uppercase;
  white-space:nowrap;
}

/* The nav sits beside the lockup rather than under it. */
nav.site{ flex:none; margin-top:0; }

/* ── The menu button ───────────────────────────────────────────────────────
   Only ever on a stacked masthead, and only once menu.js has unhidden it. The
   markup ships it hidden so that a reader with no JavaScript gets the open
   menu rather than a button that cannot open one. */
.menu-button{
  display:none; flex:none; align-items:center; justify-content:center;
  width:44px; height:44px; margin-right:calc(var(--s2) * -1);
  padding:0; border:0; background:none; color:var(--ink); cursor:pointer;
}
.menu-button:hover{ color:var(--accent); }
/* Three rules drawn with the element's own box and its two pseudo-elements,
   so the icon costs no request and inherits colour like text. */
.menu-bars, .menu-bars::before, .menu-bars::after{
  display:block; width:22px; height:1.5px; background:currentColor;
  transition:transform var(--ease), opacity var(--ease);
}
.menu-bars::before, .menu-bars::after{ content:""; position:relative; }
.menu-bars::before{ top:-7px; }
.menu-bars::after{ top:5.5px; }
/* Open, the bars become a cross. Same three rules, rotated: a reader watching
   the change sees one object move rather than two icons swap. */
[aria-expanded="true"] .menu-bars{ background:transparent; }
[aria-expanded="true"] .menu-bars::before{ top:1.5px; transform:rotate(45deg); }
[aria-expanded="true"] .menu-bars::after{ top:0; transform:rotate(-45deg); }

@media (max-width:800px){
  /* Wordmark and button share the first row; the nav takes the whole of the
     next one, which is what gives it somewhere to open into. */
  header.site .wrap{
    display:flex; flex-wrap:wrap; align-items:center;
    justify-content:space-between;
    /* Padding on both sides now. It used to be zero at the bottom because the
       open nav supplied the space; collapsed behind a button there is nothing
       under the wordmark and it sat on the rule. */
    padding-block:var(--s3);
    /* And a smaller gap than the desktop row, which is the whole reason the
       button kept dropping under the logo at 320px. The 32px set above was
       never overridden here, so the first row was asking for a 221px wordmark
       plus 32px of gap plus the button inside 272px of content box. Twelve
       fits, with the wordmark at full size. */
    gap:var(--s2);
  }
  /* Room for the button on the same row, and no more than it needs.
     "width:min(100%, 390px)" resolved to the full width on a phone, which
     filled row one and pushed the button onto row two.

     Reserving 56px then over-corrected, and at 320px that mattered: the
     wordmark is white-space:nowrap and 221px wide at this size, the cap left
     it 216px, so it overflowed by five pixels and the row wrapped anyway --
     the button back at the left gutter, under the logo, on exactly the
     narrow screens with least room to spare.

     44px is the button's own width; the -12px margin below pulls the icon
     out to the optical edge, so it occupies 32px and this leaves 12px of air
     between the two. Every width from 360 up had room to spare either way;
     320 is the one that did not. */
  .brand{ flex:1 1 auto; min-width:0; max-width:calc(100% - 44px); }
  /* The floor drops from 1.75rem so the viewport term can take over on the
     narrowest screens. Above about 330px the 7.5vw is larger and nothing
     changes; below it the wordmark gives a little rather than shoving the
     button onto its own row. */
  .brand-name{ font-size:clamp(1.55rem, 7.5vw, 2.1rem); }
  nav.site{ flex:0 0 100%; margin-top:var(--s3); }
  /* Keyed on html.js, which an inline script in <head> sets before anything
     paints -- see the comment on it in base.html. Two things follow from that
     and both matter:

     Nothing here applies when the script did not run, so a reader with no
     JavaScript gets no button and an open menu, which is the same contract
     menu.js used to keep with a hidden attribute.

     And because the class is in place before the first paint, the collapsed
     masthead is what the browser draws rather than what it redraws. The old
     arrangement painted the menu open and let the script shut it, which threw
     the page 146px up the screen on a slow connection. */
  .js .menu-button{ display:flex; }
  .js nav.site{ display:none; }
  .js nav.site.open{ display:block; }
  /* Opened from a button, the menu is a stack rather than a wrapped row: one
     destination per line, each a full-width target with a rule under it. A
     two-row wrap is what the menu looks like when it is always open and there
     is no button to have pressed. */
  .js nav.site.open ul{ display:block; border-top:1px solid var(--line); }
  .js nav.site.open li{ display:block; border-bottom:1px solid var(--line); }
  .js nav.site.open a{ display:block; padding-block:var(--s3); }
}

@media (max-width:390px){
  .brand-meta{ grid-template-columns:minmax(48px, 92px) auto; }
  .brand-tagline{ font-size:.5625rem; letter-spacing:.14em; }
}

nav.site ul{ list-style:none; margin:0; padding:0; display:flex; flex-wrap:wrap; }
nav.site li{ display:flex; align-items:center; }
/* 16px each side rather than 24. Five separators at the wider setting spent
   245px of a row that only has about 620px to give, which is more than the
   three longest labels put together. */
nav.site li + li::before{
  content:""; width:1px; height:15px; background:var(--line);
  margin-inline:var(--s3); flex:none;
}
nav.site a{
  display:inline-block;
  color:var(--ink);
  text-decoration:none;
  font-size:.9375rem;
  padding-bottom:0;
  transition:color var(--ease);
}

/* Below the point where six items stop fitting on one line, the separators
   have to go. They are drawn by li + li, which counts source order and not
   rows, so every wrapped row began with a hairline hanging off its left edge
   and the menu read as broken rather than as wrapped. Space separates instead,
   which is what space is for when there is a line break doing the grouping. */
@media (max-width:800px){
  nav.site ul{ gap:0 var(--s5); }
  nav.site li + li::before{ display:none; }
  nav.site a{ padding-block:var(--s2); }
}
nav.site a:hover{ color:var(--accent); }
nav.site a[aria-current="page"]{ color:var(--accent); font-weight:600; }

/* ── Hero ──────────────────────────────────────────────────────────────────
   Only the homepage carries one. The title steps up to the 48-56px the rest of
   the site never uses, which is the whole point of having a hero at all. */

.hero{
  /* Full-bleed. See the .bleed rule below for how, and why the old version was
     wrong: pulling out by one gutter only reaches the window edge on a window
     that happens to be shell-plus-two-gutters wide. On anything wider the band
     stopped short and read as a stray box floating in the page. */
  padding-block:var(--s7) var(--s6);
  margin-bottom:var(--s7);
  border-bottom:1px solid var(--line);
  background:linear-gradient(180deg, var(--tint-hero) 0%, var(--paper) 94%);
}
.hero{ text-align:center; }
.hero h1{
  /* Light, not bold. Fraunces at 300 is the cut this face is for -- the
     high-contrast thins are what make it look set rather than typed, and they
     only appear at display size. At 700 it reads as a heavy web headline that
     could be any serif. */
  font-size:clamp(2.4rem, 1.3rem + 4.4vw, 4.3rem);
  font-weight:300;
  letter-spacing:-0.025em;
  line-height:1.04;
  max-width:22ch;
  margin:0 auto var(--s3);
}
/* The second clause set in the true italic. Not emphasis -- the whole phrase is
   the headline -- but a change of voice, which is what an italic is for and why
   the italic subset was worth carrying.

   On its own line, because balancing the two lines by length broke the sentence
   at "see how / many", splitting a phrase across the turn. The comma is where
   the sense breaks and now it is where the line breaks too. */
.hero h1 em{ display:block; font-style:italic; font-weight:300; }
.hero .lede{ font-size:1.0625rem; line-height:1.6; color:var(--muted);
             max-width:52ch; margin:0 auto var(--s5); }
.hero .searchbar{ max-width:600px; margin-inline:auto; text-align:left; }
.hero .searchbar .controls{ max-width:none; }
.hero .hint{ text-align:center; }
/* The application deadline. Quiet, but the one date on the page a reader may be
   counting down to, so the date itself is ink and the qualification is not. */
/* ── The deadline band ─────────────────────────────────────────────────────
   Under the masthead, above everything else, for the part of the year it
   applies to. Quiet by default: this is a date a reader may be counting down
   to, not an offer, and a bar that shouts gets treated like one that is
   selling something. */
.deadline-bar{
  background:var(--accent-wash);
  border-bottom:1px solid var(--line);
}
.deadline-bar p{
  margin:0;
  padding-block:var(--s1);
  max-width:none;
  font-size:.8125rem;
  line-height:1.5;
  color:var(--ink);
}
.deadline-bar strong{ font-weight:600; }
.deadline-bar span{ color:var(--muted); }
/* The qualification is still its own element so it wraps as a unit on a narrow
   screen rather than breaking mid-clause, but it gets an ordinary sentence
   space and nothing more. A 12px indent on top of the space that is already
   there read as a gap between two unrelated notices rather than as the second
   half of one sentence pair. Colour does the separating. */
/* Inside a fortnight the band takes the accent rule. Still not a colour change
   to the text, and still not red: this is a date approaching, not a warning,
   and the site does not use colour to tell a reader how to feel. */
.deadline-bar.close{ border-top:2px solid var(--accent); }
@media (min-width:640px){ .hero{ padding-top:80px; } }
/* Flush against the masthead. main.wrap opens with 48px of top padding, which
   is right for a page that starts with a heading and wrong for one that starts
   with a full-width tinted band: it left a strip of paper between the white
   masthead and the hero, so the two read as separate objects with a bar
   floating between them rather than as one block. Pulling the hero up by
   exactly that padding closes it, and the masthead's own bottom rule becomes
   the join. */
.hero:first-child{ margin-top:calc(-1 * var(--s6)); }
/* Below 640 the silhouette would sit under the headline rather than beside it. */

/* ── Overlabel ─────────────────────────────────────────────────────────────
   A tiny spaced cap above a heading, naming which kind of thing the section
   is. Kept structural rather than editorial: it says "coverage" or "national
   picture", never a claim, because a label above a heading is read as part of
   the heading and this site does not get to editorialise in furniture. */

.overlabel{
  display:block;
  font-size:.6875rem;
  font-weight:600;
  letter-spacing:.12em;
  text-transform:uppercase;
  color:var(--accent);
  margin:0 0 var(--s1);
}
h2 .overlabel, h3 .overlabel{ font-family:var(--sans); }

/* ── Section heading with an icon ──────────────────────────────────────────
   Thin-stroke line icons, drawn inline so they inherit currentColor and scale
   with the heading. They mark the kind of section and carry no information: a
   reader who cannot see them loses nothing, which is why every one is
   aria-hidden. */

.h-icon{ display:flex; align-items:center; gap:var(--s2); }
/* A section break, not just a heading.
   
   A hairline the full width of the column with an accent segment leading it:
   the line says "a section ended here", which a heading alone does not on a
   page carrying fifteen of them, and the accent segment keeps the colour.
   
   Direct children only. The category-of-concern alert contains an h2 of its
   own, and a rule drawn inside that box would read as damage. */
main.wrap > h2:not(.h-icon)::before,
main.wrap > h2.h-icon > span::before{
  content:""; display:block; height:2px; margin-bottom:var(--s4);
  background:linear-gradient(90deg, var(--accent) 0 44px, var(--line) 44px);
}
/* The first heading on a page follows the title, which is separation enough. */
main.wrap > h2:first-of-type::before{ background:var(--accent); width:44px; }
.h-icon svg{ flex:none; width:1em; height:1em; stroke:var(--accent); fill:none;
             stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round; opacity:.85; }

/* ── Section tints ─────────────────────────────────────────────────────────
   A full-bleed wash behind a section, to tell a reader one has ended without
   drawing a line across the page. The negative margin pulls the tint out to
   the window edge while the content stays in the shell. */

.tinted{
  background:var(--tint-panel);
  border-top:1px solid var(--line);
  border-bottom:1px solid var(--line);
  padding-block:var(--s6);
}
.tinted > :first-child{ margin-top:0; }

/* ── Full bleed ────────────────────────────────────────────────────────────
   A band whose background reaches both window edges while its contents stay
   lined up with the rest of the page.

   The arithmetic, because it looks like a trick and is not: these are children
   of main.wrap, so a percentage here resolves against main.wrap's *content*
   box -- the shell minus its two gutters. Pulling the margin out by
   50% - 50vw widens the element to exactly the window, and putting the same
   number back as padding lands the text on the gutter it started on. No magic
   numbers, and it stays correct when the shell or the gutter changes.

   The clip below is the scrollbar guard. Where a scrollbar takes real space,
   100vw counts it and 50% does not, so the band would end up a scrollbar wider
   than the window and put a horizontal scrollbar on every page. `clip` rather
   than `hidden`: hidden would make the body a scroll container and break any
   future sticky positioning. */
.hero, .tinted{
  margin-inline:calc(50% - 50vw);
  padding-inline:calc(50vw - 50%);
}
html{ overflow-x:clip; }

/* ── Stat cards ────────────────────────────────────────────────────────────
   Number, label, the shape behind it, and one sentence saying what the shape
   means. The sentence is the fact; the sparkline is the shape. See the note on
   sparklines at the top of catchment/api/charts.py for why both are here. */

.stat-grid{
  list-style:none; margin:var(--s4) 0 var(--s3); padding:0; max-width:none;
  display:grid; gap:var(--s3);
  grid-template-columns:repeat(auto-fit, minmax(240px, 1fr));
}
.stat-card{
  --card:var(--series-1);
  position:relative;
  background:var(--panel);
  border:1px solid var(--line);
  border-radius:3px;
  padding:var(--s4);
  margin:0;
  box-shadow:0 1px 2px rgba(15, 17, 21, .04);
  overflow:hidden;
}
/* The rule on top is the card's own colour, and so are its number and its
   sparkline. Three cards, three metrics, three colours from the checked
   palette: the colour says which card you are reading, never whether the
   number in it is good news. */
.stat-card::before{
  content:""; position:absolute; inset:0 0 auto; height:3px; background:var(--card);
}
.stat-card:nth-child(2){ --card:var(--series-2); }
.stat-card:nth-child(3){ --card:var(--series-3); }
.stat-card .value{
  display:block;
  font-family:var(--serif);
  font-size:2.125rem;
  font-weight:300;
  line-height:1.1;
  letter-spacing:-0.025em;
  font-variant-numeric:tabular-nums;
  color:var(--card);
}
.stat-card .spark svg path{ stroke:var(--card); }
.stat-card .name{
  display:block; font-size:.8125rem; font-weight:600; letter-spacing:.06em;
  text-transform:uppercase; color:var(--muted); margin-bottom:var(--s2);
}
.stat-card .spark{ display:block; margin:var(--s2) 0 var(--s1); }
.stat-card .spark svg{ display:block; }
.stat-card .insight{ margin:0; font-size:.875rem; line-height:1.5; color:var(--muted); }
.stat-card .insight strong{ color:var(--ink); font-weight:600; }
/* A card whose value is words rather than a figure -- an Ofsted grade. It gets
   the same frame and a size that fits a phrase, because "Requires improvement"
   at 34px wraps to three lines and stops looking like a value. */
.stat-card.text .value{ font-size:1.375rem; line-height:1.25; }

/* ── Featured chart panel ──────────────────────────────────────────────────
   The one component on the site allowed to look like a feature. Accent rule on
   top, the faintest shadow, and a message-led title set as a heading rather
   than as a caption -- a chart whose title names its topic instead of its
   finding makes the reader do the work the chart was supposed to do. */

.chart-panel{
  background:var(--panel);
  border:1px solid var(--line);
  border-top:3px solid var(--accent);
  border-radius:3px;
  padding:var(--s4);
  margin:var(--s4) 0;
  box-shadow:0 1px 3px rgba(15, 17, 21, .05);
}
.chart-panel h3{ margin:0 0 var(--s1); font-size:1.25rem; line-height:1.3; max-width:34ch; }
.chart-panel .standfirst{ margin:0 0 var(--s4); font-size:.9375rem; color:var(--muted); max-width:62ch; }
.chart-panel figure{ margin:0; }
.chart-panel svg{ display:block; width:100%; height:auto; }
.chart-panel figcaption{ margin-top:var(--s3); font-size:.75rem; line-height:1.55; color:var(--muted); }
/* A map in a panel is a tall shape, not a wide one like every chart here, so it
   is capped and centred rather than stretched to the panel's width. Without the
   cap a 772x1000 map on a desktop panel is a metre of England. */
.chart-panel img{ display:block; width:100%; max-width:380px; height:auto; margin:0 auto; }
.chart-panel text{ font-family:var(--sans); font-variant-numeric:tabular-nums; }
@media (min-width:640px){ .chart-panel{ padding:var(--s5); } }
/* The plot needs room for its direct labels; below this it is scrolled rather
   than squeezed, because squeezed is where the labels start overlapping. */
.chart-scroll{ overflow-x:auto; }
.chart-scroll > svg{ min-width:520px; }

/* ── Search ────────────────────────────────────────────────────────────────
   One component, used by the hero, /find and /search, so the control a parent
   types into is the same size and shape wherever they meet it. */

.searchbar{ margin:0 0 var(--s3); }
.searchbar label{ display:block; font-size:.9375rem; font-weight:600; margin-bottom:var(--s1); }
.searchbar .controls{ display:flex; gap:var(--s2); flex-wrap:wrap; max-width:560px; }
.searchbar input{
  flex:1 1 15rem;
  min-width:0;
  height:52px;
  padding:0 var(--s3);
  font:inherit;
  font-size:1rem;
  color:var(--ink);
  background:var(--panel);
  border:1px solid var(--line);
  border-radius:4px;
  transition:border-color var(--ease), box-shadow var(--ease);
}
.searchbar input::placeholder{ color:var(--muted); opacity:.75; }
/* The outline is replaced rather than removed: a 1px border going darker is not
   a focus state anyone can see, so the ring does the work and is tinted with the
   accent so it stays visible against both the paper and a white panel. */
.searchbar input:focus{
  outline:none;
  border-color:var(--accent);
  box-shadow:0 0 0 3px rgba(31, 78, 121, .22);
}
.searchbar button{
  height:52px;
  padding:0 var(--s4);
  font:inherit;
  font-size:.9375rem;
  font-weight:600;
  color:#fff;
  background:var(--accent);
  border:1px solid var(--accent);
  border-radius:4px;
  cursor:pointer;
  transition:background var(--ease), border-color var(--ease);
}
.searchbar button:hover{ background:var(--accent-deep); border-color:var(--accent-deep); }
.searchbar .hint{ margin:var(--s1) 0 0; }

/* ── Column list ───────────────────────────────────────────────────────────
   151 councils. Set by column *width* rather than count, so the browser picks
   four across a desktop and one on a phone without a breakpoint per step. */

.col-list{
  list-style:none;
  margin:0 0 var(--s3);
  padding:0;
  max-width:none;
  columns:16rem;
  column-gap:var(--s5);
}
.col-list li{ break-inside:avoid; margin:0; padding:var(--s1) 0; font-size:.9375rem; line-height:1.4; }
.col-list a{ color:var(--ink); text-decoration:none; border-bottom:1px solid transparent; transition:border-color var(--ease), color var(--ease); }
.col-list a:hover{ color:var(--accent); border-bottom-color:var(--accent); }
.col-list .label{ font-size:.9375rem; font-variant-numeric:tabular-nums; }

/* ── Tables ────────────────────────────────────────────────────────────────
   The component the site exists for. No vertical rules, no zebra fill: light
   horizontal rules and a heavier one under the header, which is what a printed
   statistical table does and the reason those read as authoritative.

   `font-variant-numeric` is set here rather than on `.num`, so a column of
   figures somebody forgot to mark up still lines up. */

.scroll{ overflow-x:auto; margin:0 0 var(--s2); }
table{
  border-collapse:collapse;
  /* Natural width, not the full shell. A five-column table stretched across
     1,120px puts a thousand pixels of paper between "2025" and the figure on
     that row, and the eye loses the line. `auto` lets the table be as wide as
     its contents need; the floor stops a two-column table from shrinking to a
     stub, and the ceiling hands anything genuinely wider to .scroll. */
  width:auto;
  min-width:min(100%, 34rem);
  max-width:100%;
  font-size:.9375rem;
  line-height:1.5;
  font-variant-numeric:tabular-nums;
}
/* Columns of prose -- a school name, a council's own name for one of its
   admission rules -- take the slack, so the figures beside them stay in a
   tight band. */
td:first-child{ max-width:28rem; }
caption{
  text-align:left;
  font-size:.9375rem;
  font-weight:600;
  color:var(--ink);
  padding-bottom:var(--s2);
  /* A caption takes the width of its table, and a table inside .scroll can be
     wider than the phone it is being read on -- which put the title off the
     right edge of the screen until the reader scrolled the table sideways.
     Holding it to the viewport makes it wrap instead. */
  max-width:calc(100vw - 2 * var(--gutter));
}
th,td{ text-align:left; padding:var(--s2) var(--s3); border-bottom:1px solid var(--line); vertical-align:top; }
th:first-child,td:first-child{ padding-left:0; }
th:last-child,td:last-child{ padding-right:0; }
thead th{
  font-weight:600;
  font-size:.875rem;
  line-height:1.4;
  color:var(--accent);
  background:var(--accent-wash);
  border-bottom:1.5px solid var(--accent);
  padding-block:var(--s1);
  vertical-align:bottom;
}
/* The tint has to reach the edge of the table, and the first and last cells
   have their side padding removed so the columns line up with the text above. */
thead th:first-child{ padding-left:var(--s2); }
thead th:last-child{ padding-right:var(--s2); }
tbody td:first-child, tfoot td:first-child{ padding-left:var(--s2); }
tbody td:last-child, tfoot td:last-child{ padding-right:var(--s2); }
tbody tr{ transition:background var(--ease); }
tbody tr:hover{ background:var(--row-hover); }
tbody td{ padding-block:var(--s2); }
tfoot td{ border-bottom:none; border-top:1.5px solid var(--ink); font-weight:600; }
td.num,th.num{ text-align:right; }
/* A row's second line -- a town, a framework name, a confidence range -- is
   subordinate to the value above it and set smaller so the column still
   scans. */
td .label{ font-size:.8125rem; line-height:1.4; }

/* The one ranked table on the site. Two columns carry the ranking and nothing
   else does, so they are weighted by position rather than by a class on every
   cell: the rank recedes to a muted index, and the number the rows are sorted
   by is the largest thing in the row. That is the whole visual treatment this
   page gets -- see the note in catchment/api/charts.py about the bar chart it
   deliberately does not have. */
table.ranking td:first-child{ color:var(--muted); width:3rem; }
table.ranking td:nth-child(3){ font-size:1.0625rem; font-weight:600; }

/* The two-column "what is here" table: a metric and a number, nothing else, so
   the rows can breathe more than a data table's. It is held narrow on purpose --
   across the full shell the label and its figure end up 900px apart, and a
   reader has to track across empty paper to pair them up. */
table.stats{ max-width:36rem; }
table.stats td{ padding-block:var(--s3); }
table.stats td:last-child{ font-size:1.0625rem; font-weight:600; white-space:nowrap; }

/* ── The homepage band ─────────────────────────────────────────────────────
   A picture with a way in on either side. Both side columns are routes to a
   school -- a ranking on the left, the councils on the right -- so nothing in
   the band is about the site rather than about schools.

   Three columns only where there is room for three. Below 960px they stack, and
   the order in the markup is the order that matters on a phone: ranking, then
   map, then councils. */

.band{ display:grid; gap:var(--s6); margin:var(--s6) 0 var(--s7); }
@media (min-width:960px){
  .band{ grid-template-columns:1fr 1.7fr 1fr; gap:var(--s6); align-items:start; }
}
.band-col{ min-width:0; }
.band-h{
  /* Italic serif, muted. These label a column rather than open a section, and
     the preview's italic is what tells them apart from the headings that do. */
  font-family:var(--serif); font-style:italic; font-weight:400;
  font-size:1.0625rem; color:var(--muted); letter-spacing:0;
  margin:0 0 var(--s2); padding-top:var(--s3);
  border-top:1px solid var(--ink);
}
.band-h::before{ content:none; }
.band-note{ font-size:.8125rem; line-height:1.5; color:var(--muted); margin:0 0 var(--s3); }
.band-note strong{ color:var(--ink); }

/* The ranking. A number, a school, a ratio -- nothing else fits in a column
   this narrow and nothing else is needed to rank by one figure. */
.phase-pick{ display:flex; border:1px solid var(--line); background:var(--panel);
             width:max-content; margin:0 0 var(--s2); }
.phase-pick button{
  font:inherit; font-size:.75rem; padding:5px 12px; border:0; background:none;
  color:var(--muted); cursor:pointer; transition:background var(--ease), color var(--ease);
}
.phase-pick button[aria-pressed="true"]{ background:var(--ink); color:var(--panel); }
/* The column key. A rule under it in ink rather than the hairline the rows
   use, so the list reads as a table with a head and not as five rows that
   happen to start under some small text. */
.ranked-key{
  display:flex; justify-content:space-between; align-items:baseline; gap:var(--s2);
  margin:0; padding-bottom:var(--s1); border-bottom:1px solid var(--ink);
  max-width:none;
  font-size:.6875rem; letter-spacing:.08em; text-transform:uppercase;
  color:var(--muted);
}
.ranked{ list-style:none; margin:0; padding:0; max-width:none; }
.ranked li{
  display:flex; align-items:baseline; gap:var(--s2);
  padding:var(--s2) 0; border-bottom:1px solid var(--line); margin:0;
}
.ranked .pos{ font-family:var(--serif); font-size:.9375rem; color:var(--muted);
              width:.9rem; flex:none; }
.ranked .who{ flex:1; min-width:0; }
.ranked .who a{ display:block; font-size:.875rem; font-weight:500; line-height:1.3;
                color:var(--ink); text-decoration:none; }
.ranked .who a:hover{ color:var(--accent); text-decoration:underline; }
.ranked .where{ display:block; font-size:.75rem; color:var(--muted); margin-top:2px; }
.ranked .counts{ display:block; font-size:.6875rem; color:var(--muted); margin-top:1px;
                 font-variant-numeric:tabular-nums; }
.ranked .ratio{ font-family:var(--serif); font-size:1.0625rem; flex:none;
                font-variant-numeric:tabular-nums; }

/* The map. It is the only thing in the band that is not a list, and it carries
   the colour for the whole page. */
.band-art{ text-align:center; }
.art-h{ font-family:var(--serif); font-style:italic; font-weight:400;
        font-size:1.1875rem; margin:0 0 var(--s1); letter-spacing:0; }
.art-h::before{ content:none; }
.art-sub{ font-size:.8125rem; color:var(--muted); margin:0 auto var(--s3);
          max-width:44ch; line-height:1.5; }
/* The map is an <object>, which has no intrinsic size to lay out from the way
   an <img> does, so the ratio is stated here and matches the viewBox. Getting
   this wrong is a layout shift on every first paint, not a wrong-looking map. */
.band-art object{ display:block; width:100%; max-width:440px; aspect-ratio:77.2/100;
                  margin:0 auto; }
.band-art img{ display:block; width:100%; max-width:440px; height:auto; margin:0 auto; }
.ramp{ list-style:none; display:flex; align-items:center; justify-content:center;
       gap:var(--s2); margin:var(--s3) 0 0; padding:0; max-width:none;
       font-size:.6875rem; letter-spacing:.08em; text-transform:uppercase;
       color:var(--muted); }
.ramp li{ margin:0; display:flex; gap:2px; }
.ramp i{ width:26px; height:9px; }
.art-figs{ display:flex; justify-content:center; flex-wrap:wrap; gap:var(--s4) var(--s5);
           margin:var(--s4) 0 0; padding-top:var(--s4); border-top:1px solid var(--line); }
.art-figs p{ margin:0; }
.art-figs b{ display:block; font-family:var(--serif); font-size:1.9rem; font-weight:300;
             line-height:1; letter-spacing:-.025em; font-variant-numeric:tabular-nums lining-nums; }
.art-figs span{ display:block; font-size:.6875rem; letter-spacing:.1em;
                text-transform:uppercase; color:var(--muted); margin-top:var(--s1); }

/* The councils. */
.council-list{ list-style:none; margin:0; padding:0; max-width:none; }
.council-list li{ margin:0; }
.council-list a{
  display:flex; justify-content:space-between; align-items:baseline; gap:var(--s2);
  padding:var(--s2) 0; border-bottom:1px solid var(--line);
  color:var(--ink); text-decoration:none; font-size:.875rem;
  transition:color var(--ease);
}
.council-list a:hover{ color:var(--accent); }
.council-list span{ color:var(--muted); font-size:.75rem; font-variant-numeric:tabular-nums; }

/* ── Trust strip ───────────────────────────────────────────────────────────
   Where the coverage figures live now. They answer "can I believe this", which
   a reader asks after they have seen something worth believing, not before. */
/* The trust strip and the footer are one closing block, not two.
   
   They used to be two: a 2px ink rule here, 64px of main's bottom padding, then
   another 2px rule and 64px of the footer's own margin. Two thick lines with
   128px of dead cream between them read as two disconnected slabs rather than
   as the foot of a page. One rule opens the block now -- coral, answering the
   masthead's -- and a hairline separates the parts inside it. */
.trust{
  border-top:2px solid var(--accent);
  background:var(--panel);
  margin-inline:calc(50% - 50vw); padding:var(--s4) calc(50vw - 50%);
}
/* Scoped with :has so only a page that ends in the strip loses the gap; every
   other page keeps the space between its last section and the footer. Where
   :has is missing this degrades to the old spacing rather than breaking. */
main.wrap:has(> .trust:last-child){ padding-bottom:0; }
/* `~` and not `+`: the page puts its enhancement scripts between main and the
   footer, and an adjacent-sibling selector silently misses across them. */
main.wrap:has(> .trust:last-child) ~ footer.site{
  margin-top:0;
  border-top:1px solid var(--line);
}
main.wrap:has(> .trust:last-child) ~ footer.site .wrap{ padding-block:var(--s5) var(--s6); }
.trust ul{ list-style:none; margin:0; padding:0; max-width:none;
           display:flex; flex-wrap:wrap; gap:var(--s2) var(--s5);
           align-items:baseline; font-size:.8125rem; color:var(--muted); }
.trust li{ margin:0; }
.trust b{ color:var(--ink); font-weight:600; font-variant-numeric:tabular-nums; }
.trust .last{ margin-left:auto; }

/* ── Contents rail ─────────────────────────────────────────────────────────
   A school page runs to about 13,000px across as many as nineteen sections,
   and until this there was no way to see that structure or to jump within it.

   It only appears above 1200px, where there is genuinely margin to put it in.
   Narrower than that the page is the rail: one column, scrolled. The grid is
   applied only to pages that ask for it, because the full-bleed bands on the
   homepage size themselves against main.wrap and a second column would move
   the edge they bleed to. */
.contents{ display:none; }

@media (min-width:1200px){
  /* The rail is positioned, not placed in a grid.
     
     It was a grid column with `grid-row: 1 / -1`, and that is the bug this
     replaces: -1 resolves against the *explicit* grid, which here has no rows
     at all, so the span collapsed to a single row -- and that row was then
     sized to fit a 709px rail. The h1 shared it, which put 660px of empty paper
     under the school name on every school and council page.
     
     Absolute in a reserved gutter has no such failure mode: the rail cannot
     size a row it is not in. The inner wrapper is what sticks, and it can stick
     for the whole height because its container is the full column. */
  main.wrap.has-rail{
    /* One number, used by both rules below, so the reserved space and the rail
       that sits in it cannot drift apart. */
    --rail:208px;
    position:relative;
    /* Gutter + rail + a column gap. Setting padding-right to just the rail plus
       a gap *replaced* the gutter rather than adding to it, which left the text
       column ending at exactly the pixel the rail began -- the two touched, and
       long lines ran straight into the list of section links. */
    padding-right:calc(var(--gutter) + var(--rail) + var(--s6));
  }
  main.wrap.has-rail > .contents{
    display:block;
    position:absolute;
    top:var(--s6);
    bottom:var(--s7);
    right:var(--gutter);
    width:var(--rail);
  }
  main.wrap.has-rail > .contents[hidden]{ display:none; }
  .contents-inner{
    position:sticky;
    top:var(--s4);
    max-height:calc(100vh - var(--s7));
    overflow-y:auto;
    font-size:.8125rem;
    line-height:1.45;
  }
  .contents > .contents-inner > h2{
    font-family:var(--sans);
    font-size:.6875rem;
    font-weight:600;
    letter-spacing:.12em;
    text-transform:uppercase;
    color:var(--muted);
    margin:0 0 var(--s2);
    padding:0;
    border-top:none;
  }
  .contents > .contents-inner > h2::before{ content:none; }
  .contents ol{
    list-style:none;
    margin:0;
    padding:0;
    border-left:2px solid var(--line);
  }
  .contents li{ margin:0; }
  .contents a{
    display:block;
    padding:var(--s1) 0 var(--s1) var(--s2);
    margin-left:-2px;
    border-left:2px solid transparent;
    color:var(--muted);
    text-decoration:none;
    transition:color var(--ease), border-color var(--ease);
  }
  .contents a:hover{ color:var(--accent); border-left-color:var(--line); }
  .contents a[aria-current]{ color:var(--accent); font-weight:600; border-left-color:var(--accent); }
}

/* ── Grade grid ────────────────────────────────────────────────────────────
   Ofsted publishes up to fourteen graded areas, and they were printed as
   fourteen consecutive sentences -- "Safeguarding: Met", "Inclusion: Expected
   standard" -- which is a table read aloud. As a grid the reader can run down
   the grades without reading the labels twice.

   No colour on the grades. Which is the whole point: a green "Good" and a red
   "Needs attention" would be this site making the judgement Ofsted's words
   already make, and it is the rule that every figure here obeys. */
dl.grades{
  display:grid;
  grid-template-columns:minmax(10rem, max-content) minmax(0, 1fr);
  gap:0 var(--s4);
  margin:var(--s3) 0 var(--s4);
  max-width:var(--measure);
  font-size:.9375rem;
}
dl.grades dt{
  color:var(--muted);
  padding:var(--s2) 0;
  border-top:1px solid var(--line);
}
dl.grades dd{
  margin:0;
  font-weight:600;
  padding:var(--s2) 0;
  border-top:1px solid var(--line);
  /* "About the school" puts a school's website here, and some are one long
     unbroken address that widened the whole page on a phone. */
  min-width:0;
  overflow-wrap:anywhere;
}
@media (max-width:480px){
  dl.grades{ grid-template-columns:1fr; gap:0; }
  dl.grades dt{ padding-bottom:0; }
  dl.grades dd{ border-top:none; padding-top:2px; }
}

/* ── Figures ───────────────────────────────────────────────────────────────
   Every inline SVG on the site sits in one of these: the drawing, then a
   caption in the same quiet as a source line. The figures themselves are built
   in catchment/api/charts.py, one accent series against grey context, and they
   never carry a fact the page does not also state in words. */

figure.chart{ margin:var(--s4) 0; max-width:var(--measure); }
figure.chart svg{ display:block; max-width:100%; height:auto; }
figure.chart figcaption{ color:var(--muted); font-size:.8125rem; line-height:1.55; margin-top:var(--s1); }
figure.chart text{ font-family:var(--sans); font-variant-numeric:tabular-nums; }

/* ── Panels ────────────────────────────────────────────────────────────────
   `.qa` carries the sentence a page is really answering. It is a panel rather
   than a rule-and-indent so it holds its weight next to a table. */

.qa{
  background:var(--panel);
  border:1px solid var(--line);
  border-left:3px solid var(--accent);
  border-radius:2px;
  padding:var(--s4);
  margin:var(--s4) 0;
}
.qa > :last-child{ margin-bottom:0; }
.qa p{ margin:0 0 var(--s2); }
.qa > p:first-of-type{ font-size:1.0625rem; }
@media (min-width:640px){ .qa{ padding:var(--s5); } }
/* One result among many, on /find. Same card, no accent: the accent means "this
   is the sentence the page is answering", and a list of twelve cannot each be
   that. */
.qa.result{ border-left:1px solid var(--line); padding:var(--s4); margin:0 0 var(--s2); }
.qa.result p{ max-width:none; }

/* An Ofsted category of concern. The heaviest thing on any page, and the only
   place an ink border is used, because it should not look like the panels. */
.alert{
  background:var(--panel);
  border:1.5px solid var(--ink);
  border-radius:2px;
  padding:var(--s4);
  margin:var(--s5) 0;
}
.alert h2{ margin:0 0 var(--s2); font-size:1.25rem; }
.alert p{ margin:0 0 var(--s2); }
.alert > :last-child{ margin-bottom:0; }
@media (min-width:640px){ .alert{ padding:var(--s5); } }

/* ── Filter pills ──────────────────────────────────────────────────────── */

nav.filters{ margin:var(--s4) 0 var(--s5); }
nav.filters ul{ list-style:none; margin:0; padding:0; display:flex; gap:var(--s1); flex-wrap:wrap; }
.filter{
  display:inline-block;
  padding:var(--s1) var(--s3);
  border:1px solid var(--line);
  border-radius:999px;
  background:var(--panel);
  text-decoration:none;
  font-size:.875rem;
  line-height:1.4;
  color:var(--ink);
  transition:border-color var(--ease), color var(--ease);
}
a.filter:hover{ border-color:var(--accent); color:var(--accent); background:var(--accent-wash); }
.filter.current{ background:var(--accent); color:#fff; border-color:var(--accent); font-weight:600; }
.filter .count{ color:var(--muted); font-variant-numeric:tabular-nums; margin-left:var(--s1); }
.filter.current .count{ color:var(--panel); opacity:.72; }

/* ── Maps ──────────────────────────────────────────────────────────────── */

.map{ border:1px solid var(--line); border-radius:2px; margin:var(--s4) 0; background:var(--panel); }
ul.legend{
  list-style:none; margin:var(--s2) 0 var(--s4); padding:0; max-width:none;
  display:flex; gap:var(--s1) var(--s4); flex-wrap:wrap;
  font-size:.8125rem; line-height:1.5; color:var(--muted);
}
ul.legend li{ display:flex; align-items:center; gap:var(--s1); margin:0; }
.swatch{ display:inline-block; width:14px; height:14px; border:1px solid var(--line); flex:none; }

/* ── Footer ────────────────────────────────────────────────────────────── */

footer.site{
  background:var(--panel);
  border-top:2px solid var(--accent);
  margin-top:var(--s7);
}
footer.site .wrap{ padding-block:var(--s6) var(--s6); }
footer.site p{ font-size:.8125rem; line-height:1.6; color:var(--muted); margin:0 0 var(--s2); max-width:var(--measure); }
footer.site nav.footer-nav{ margin:0 0 var(--s4); }
footer.site nav.footer-nav ul{ gap:var(--s4); }
footer.site nav.footer-nav a{ padding-bottom:0; border-bottom:none; }
footer.site nav.footer-nav a:hover{ color:var(--accent); }
/* The masthead marks the current page with a rule under it. Down here there is
   no rule to use, so the current page is marked in the accent instead -- the
   bold on its own was too quiet to read as "you are here". */
footer.site nav.footer-nav a[aria-current="page"]{ color:var(--accent); }
footer.site nav.footer-nav ul.machine{ margin-top:var(--s2); gap:var(--s3); }
footer.site nav.footer-nav ul.machine a{ color:var(--muted); font-size:.8125rem; }

/* A reader who has asked their system for less motion gets none of the
   transitions above; nothing here animates anything load-bearing. */
@media (prefers-reduced-motion:reduce){
  *{ transition:none !important; animation:none !important; }
}

/* ── The locator ───────────────────────────────────────────────────────────
   The figure at the top of a postcode result. Numbered to the cards under it,
   so the picture and the detail are one thing. */
figure.locator{ margin:var(--s5) 0; text-align:center; }
figure.locator svg{ display:block; width:100%; max-width:460px; height:auto; margin:0 auto; }
figure.locator figcaption{
  margin:var(--s3) auto 0; max-width:56ch;
  font-size:.75rem; line-height:1.55; color:var(--muted);
}
/* The number on a result card, matching its dot in the figure. */
.qa.result .pin{
  display:inline-flex; align-items:center; justify-content:center;
  width:1.4rem; height:1.4rem; margin-right:var(--s1);
  border:1px solid var(--ink); border-radius:50%;
  font-size:.75rem; font-weight:600; font-variant-numeric:tabular-nums;
  vertical-align:baseline;
}

/* The search control on a page that is already showing results. Smaller than
   the homepage's, because here it is a way to change the answer rather than the
   reason for being on the page. */
.searchbar.again{ max-width:520px; }
.searchbar.again input{ height:44px; font-size:1rem; }
.searchbar.again button{ height:44px; padding-inline:var(--s4); }
.searchbar.again .hint{ margin-top:var(--s1); }

/* ── Admission criteria ────────────────────────────────────────────────────
   A council may publish a rule as three words or as a 110-word paragraph, and
   the list has to stay scannable either way. The rule's own first sentence
   leads; the conditions that follow it sit underneath in the muted size. Both
   are the council's words and both are always shown -- the hierarchy is there
   so a reader can see the shape of the priority order at a glance, not so they
   can be given less of it. */
.rules .rule-lead{ display:block; }
.rules .rule-rest{ display:block; margin-top:var(--s1); font-size:.8125rem;
                   line-height:1.55; color:var(--muted); }
.rules li{ padding-block:var(--s2); }
/* Pointers into a definitions list this site does not publish. Quiet, because
   they are the council's bookkeeping rather than the rule, and still legible,
   because a reader who wants to know what "catchment area" formally means has
   to be able to see that the council defines it somewhere. */
.rules .rule-ref{ color:var(--muted); font-size:.9em; }
/* The key to the list, so it carries a little more weight than an ordinary
   section note and sits tight against the list it explains. */
.rule-order{ color:var(--ink); margin-bottom:var(--s2); }
/* The link out to a rule's conditions, and the notes they land in. Small and
   set in the accent so it reads as a pointer rather than as part of the rule:
   the words before it are the council's, this one is ours. */
.rules .rule-more{ font-size:.75rem; color:var(--accent); text-decoration:none;
                   border-bottom:1px solid var(--accent-wash); white-space:nowrap; }
.rules .rule-more:hover{ border-bottom-color:var(--accent); }
.rule-notes{ margin-top:var(--s5); padding-top:var(--s4); border-top:1px solid var(--line); }
.rule-notes h3{ font-size:.6875rem; letter-spacing:.1em; text-transform:uppercase;
                color:var(--muted); font-family:var(--sans); font-weight:500;
                margin:0 0 var(--s3); }
.rule-notes h3::before{ content:none; }
.rule-notes ol{ margin:0; padding-left:1.4em; max-width:var(--measure); }
.rule-notes li{ margin:0 0 var(--s3); font-size:.8125rem; line-height:1.6;
                color:var(--muted); }
.rule-notes li::marker{ color:var(--ink); font-variant-numeric:tabular-nums; }
.rule-back{ display:block; margin-top:var(--s1); font-size:.6875rem;
            letter-spacing:.06em; text-transform:uppercase;
            color:var(--accent); text-decoration:none; }
.rule-back:hover{ text-decoration:underline; }

/* The applications table on a school page. At phone width its five columns and
   long headings ran off the right of the screen and needed sideways scrolling
   (found in testing on 28 September 2026). Below 560px it takes short
   headings, a smaller size and tighter cells, and fits a 375px screen. */
table.demand-table .short{ display:none; }
@media (max-width:560px){
  table.demand-table{ min-width:0; width:100%; font-size:.8125rem; }
  table.demand-table th, table.demand-table td{ padding:var(--s2) .35rem; }
  table.demand-table th{ white-space:normal; line-height:1.25; }
  table.demand-table .long{ display:none; }
  table.demand-table .short{ display:inline; }
  table.demand-table .label{ font-size:.75rem; }
}

/* Tables that still scroll sideways on a phone (results, teacher turnover and
   the like). Found in testing on 29 September 2026: scrolling right lost the
   year column, so a row of figures could not be told from the next, and long
   headings were squeezed into narrow columns. Below 560px the first column
   stays put while the rest scrolls, headings wrap at a readable width, and a
   shadow at the edge shows there is more to see. */
@media (max-width:560px){
  .scroll{
    /* Edge shadows that appear only where there is content to scroll to. */
    background:
      linear-gradient(to right, var(--paper) 30%, transparent) left / 24px 100% no-repeat local,
      linear-gradient(to left, var(--paper) 30%, transparent) right / 24px 100% no-repeat local,
      radial-gradient(farthest-side at 0 50%, rgba(0,0,0,.18), transparent) left / 10px 100% no-repeat scroll,
      radial-gradient(farthest-side at 100% 50%, rgba(0,0,0,.18), transparent) right / 10px 100% no-repeat scroll;
  }
  .scroll table:not(.demand-table) th{
    white-space:normal;
    min-width:5.5rem;
    line-height:1.25;
    vertical-align:bottom;
  }
  .scroll table:not(.demand-table) th:first-child,
  .scroll table:not(.demand-table) td:first-child{
    position:sticky;
    left:0;
    z-index:1;
    background:var(--paper);
    min-width:0;
    padding-right:var(--s3);
    box-shadow:1px 0 0 var(--line);
  }
}
