/* ═══════════════════════════════════════════════════════════════════════════
   SchoolsGPT design system
   ───────────────────────────────────────────────────────────────────────────
   One identity for the whole site. Before this file, the chat shell
   (style.css) and the content pages (seo.css) were two unrelated designs that
   happened to share a logo; both now consume the tokens defined here, so a
   colour or type decision is made once and lands everywhere.

   The look is the product's original one: navy brand, a confident blue accent,
   soft rounded cards, and colour used freely — grades, badges and map pins all
   carry meaning in their colour, because that is what a parent scanning thirty
   schools actually reads first.

   A previous revision replaced this with an ink-and-chalk editorial scheme
   (display serif, mono figures, contour-line motif, one reserved accent). It
   was rejected as generic, and it measurably hurt the map, where recolouring
   the pins to a muted set made every rating look alike at pin size. Token
   NAMES survive from that revision because ~20 templates and three stylesheets
   reference them; only the values changed back.

   Sections
     1  Face           self-hosted Inter, latin subset
     2  Tokens         colour · type · space · motion · elevation
     3  Base           resets and page ground
     4  Typography     headings / lede / prose / figures
     5  Layout         containers, grid, section rhythm
     6  Components     tag · rule · button · card · stat · grade · field
     7  Motion         reveal, numeral roll
     8  Utilities
     9  Chrome         masthead, nav, footer
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1 · Faces ───────────────────────────────────────────────────────────────
   Self-hosted rather than linked from Google: the privacy page promises no
   third party sees a visitor, and a font CDN would quietly break that. Latin
   subset only — 142KB for all four files. */

@font-face {
  font-family: 'Inter';
  src: url('fonts/inter-var.woff2') format('woff2');
  font-weight: 300 700;
  font-display: swap;
  font-style: normal;
}

/* ── 2 · Tokens ──────────────────────────────────────────────────────────── */

:root {
  /* The original SchoolsGPT palette. An earlier revision replaced it with an
     ink-and-chalk scheme; it read as generic and, worse, flattened the map —
     see the grade colours below. Token NAMES are kept because ~20 templates
     and three stylesheets reference them; only the values changed back. */
  --o-paper:      #f4f6fa;   /* page ground */
  --o-surface:    #ffffff;   /* cards, popovers */
  --o-sunken:     #e8f0f8;   /* wells, chips, table stripes */
  --o-pressed:    #dbe6f2;

  --o-ink:        #0f2a44;   /* brand navy: headings, brand, strong text */
  --o-ink-soft:   #16406a;   /* secondary headings */
  --o-muted:      #64748b;   /* captions, meta */
  --o-faint:      #94a3b8;   /* marks only — under 4.5:1, never body text */

  --o-rule:       #e3e8ef;
  --o-rule-soft:  #eef2f7;

  /* Inverted panel: the page CTA and the homepage panel. A ground of its own
     rather than `--o-ink`, which is a *text* colour — using it as a surface
     works in this theme only because navy happens to suit both jobs. In dark
     mode --o-ink is near-white, so those panels turned into pale slabs while
     their hardcoded white text stayed white: measured 1.16:1. */
  --o-invert:          #0f2a44;
  --o-on-invert:       #ffffff;
  --o-on-invert-soft:  #dbe6f2;
  --o-on-invert-faint: #9fb6cc;
  --o-invert-edge:     transparent;
  --o-invert-accent:   #2563eb;   /* second stop of the bot-avatar gradient */
  --o-invert-hairline: rgba(255,255,255,.30);
  --o-invert-wash:     rgba(255,255,255,.08);

  /* Accent. Used liberally, unlike the previous revision's single reserved
     signal colour — this palette is meant to be colourful. */
  --o-signal:      #2563eb;
  --o-signal-deep: #1d4fc4;
  --o-on-signal:   #ffffff;
  --o-signal-tint: #dbeafe;
  --o-signal-edge: #93c5fd;

  /* Categorical colours: charts, badges, map pins. */
  --o-water:  #2563eb;
  --o-moss:   #15803d;
  --o-brick:  #dc2626;
  --o-heath:  #7c3aed;
  --o-sand:   #f59e0b;

  /* Inspection grades — a traffic light, deliberately.
     These also drive the map pins (hardcoded twins in map.js). A previous
     revision recoloured them to a muted moss/water/sand/brick set for
     consistency with the page; at 28px pin size every one of them read as the
     same dark blob and the map lost the signal it exists to give. Legibility
     at pin size beats palette purity here. */
  --o-grade-outstanding: #16a34a;   /* green  */
  --o-grade-good:        #eab308;   /* yellow */
  --o-grade-ri:          #f97316;   /* orange */
  --o-grade-inadequate:  #dc2626;   /* red    */
  --o-grade-none:        #2563eb;   /* blue — not rated */

  /* Type: one family. --o-serif and --o-mono are retained as names because
     both stylesheets reference them, but they now resolve to the same stack;
     the display serif and the mono were the most recognisable part of the
     look that was rejected. Figures use font-variant-numeric instead. */
  --o-sans:  'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --o-serif: var(--o-sans);
  /* Labels and figures are Inter with tabular-nums, as they were before the
     ink-and-chalk revision put everything numeric in a mono face. --o-mono is
     kept as a name because three stylesheets reference it. Real monospace
     survives only where it means something: code. */
  --o-mono:  var(--o-sans);
  --o-code:  ui-monospace, 'SF Mono', Menlo, Consolas, monospace;

  --o-t-display: clamp(1.6rem, 1.2rem + 1.9vw, 2.4rem);
  --o-t-h1:      clamp(1.45rem, 1.2rem + 1.1vw, 1.9rem);
  --o-t-h2:      clamp(1.15rem, 1.05rem + .5vw, 1.35rem);
  --o-t-h3:      1.05rem;
  --o-t-h4:      1rem;
  --o-t-lede:    clamp(.95rem, .92rem + .2vw, 1.05rem);
  --o-t-body:    .9375rem;
  --o-t-sm:      .875rem;
  --o-t-xs:      .8125rem;
  --o-t-micro:   .72rem;

  --o-measure: 68ch;

  --o-s1: 4px;   --o-s2: 8px;   --o-s3: 12px;  --o-s4: 16px;
  --o-s5: 24px;  --o-s6: 32px;  --o-s7: 48px;  --o-s8: 64px;
  --o-s9: 96px;  --o-s10: 128px;

  --o-gutter: clamp(16px, 4vw, 40px);
  --o-wide:   1180px;
  --o-mid:    980px;
  --o-narrow: 720px;
  --o-container: var(--o-wide);

  /* Rounded again — 16px cards and pill buttons are the old look. */
  --o-r1: 6px;  --o-r2: 10px;  --o-r3: 16px;  --o-r-pill: 999px;

  --o-lift1: 0 1px 3px rgba(15,42,68,.08);
  --o-lift2: 0 4px 14px rgba(15,42,68,.10);
  --o-lift3: 0 8px 28px rgba(15,42,68,.18);

  --o-dur1: 140ms;
  --o-dur2: 250ms;
  --o-dur3: 380ms;
  --o-dur4: 600ms;
  --o-ease:     cubic-bezier(.22, .61, .36, 1);
  --o-ease-ink: cubic-bezier(.65, .05, .36, 1);

  /* The masthead is a solid navy bar in both themes — the single strongest
     "this is a product, not a landing page" cue the old design had. It cannot
     use --o-ink, which inverts to near-white in dark. */
  --o-mast-bg: #0f2a44;
  --o-mast-fg: #ffffff;
  --o-mast-dim: #c3d3e3;

  --o-header-h: 60px;
  color-scheme: light;
}

/* Dark theme — a navy counterpart to the light palette above, not an
   inversion of it. The brand is already navy, so the dark ground is the same
   hue family (#0f172a) rather than a neutral black, and the accent lifts from
   #2563eb to #60a5fa because the original is unreadable on it.

   Written out twice on purpose. `light-dark()` expresses it once but is
   invalid-at-computed-value-time on older engines, which would leave every
   token unset and the site unreadable rather than merely un-themed. On a site
   whose traffic is organic search that trade is not worth twenty lines. The
   two blocks are adjacent so a diff shows when they drift.

   Note the map: OpenStreetMap tiles are light in both themes. The chrome
   around them themes, the tiles do not — filtering them to fake a dark
   basemap makes the pins and catchment shading unreadable. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --o-paper:   #0f172a;   /* deep navy, same family as the brand */
    --o-surface: #1b2739;
    --o-sunken:  #0b1220;
    --o-pressed: #243349;

    --o-ink:      #e9eff7;
    --o-ink-soft: #c3d2e6;
    --o-muted:    #94a3b8;
    --o-faint:    #64748b;

    --o-rule:      #27364c;
    --o-rule-soft: #1b2739;

    /* "Inverted" stops meaning "go light" once the page is already dark —
       a near-white slab is the brightest thing on the screen at night. The
       panel instead lifts to a blue ground a step off the cards and takes a
       visible edge, so it still reads as the end of the article. */
    --o-invert:          #1d3358;
    --o-on-invert:       #eef4fb;
    --o-on-invert-soft:  #b9cbe2;
    --o-on-invert-faint: #8fa8c4;
    --o-invert-edge:     #2f4f7d;
    --o-invert-accent:   #3b6fb8;   /* stays dark: --o-signal lightens in this theme */
    --o-invert-hairline: rgba(255,255,255,.22);
    --o-invert-wash:     rgba(255,255,255,.06);

    /* #2563eb is too dark to read on this ground, so the accent lifts a step
       and the text ON it flips to near-black. */
    --o-signal:      #60a5fa;
    --o-signal-deep: #93c5fd;
    --o-on-signal:   #0b1220;
    --o-signal-tint: rgba(96, 165, 250, .14);
    --o-signal-edge: #3b6fb8;

    --o-water:  #60a5fa;
    --o-moss:   #4ade80;
    --o-brick:  #f87171;
    --o-heath:  #c4b5fd;
    --o-sand:   #fbbf24;

    /* The traffic light stays a traffic light — brightened so each step is
       still distinguishable against a dark card. */
    --o-grade-outstanding: #22c55e;
    --o-grade-good:        #fbbf24;
    --o-grade-ri:          #fb923c;
    --o-grade-inadequate:  #f87171;
    --o-grade-none:        #60a5fa;

    --o-lift1: 0 1px 3px rgba(0,0,0,.5);
    --o-lift2: 0 4px 14px rgba(0,0,0,.55);
    --o-lift3: 0 8px 28px rgba(0,0,0,.65);
    --o-mast-bg: #13233a;
    --o-mast-fg: #ffffff;
    --o-mast-dim: #9fb6cc;
    --o-mast-bg: #13233a;
  --o-mast-fg: #ffffff;
  --o-mast-dim: #9fb6cc;
  color-scheme: dark;
  }
}

/* Second copy: a visitor who chose dark on a light-preference system. */
:root[data-theme="dark"] {
  --o-paper:   #0f172a;   /* deep navy, same family as the brand */
  --o-surface: #1b2739;
  --o-sunken:  #0b1220;
  --o-pressed: #243349;

  --o-ink:    #e9eff7;
  --o-ink-soft: #c3d2e6;
  --o-muted:  #94a3b8;
  --o-faint:  #64748b;

  --o-rule:    #27364c;
  --o-rule-soft: #1b2739;

  /* "Inverted" stops meaning "go light" once the page is already dark —
     a near-white slab is the brightest thing on the screen at night. The
     panel instead lifts to a blue ground a step off the cards and takes a
     visible edge, so it still reads as the end of the article. */
  --o-invert:          #1d3358;
  --o-on-invert:       #eef4fb;
  --o-on-invert-soft:  #b9cbe2;
  --o-on-invert-faint: #8fa8c4;
  --o-invert-edge:     #2f4f7d;
  --o-invert-accent:   #3b6fb8;   /* stays dark: --o-signal lightens in this theme */
  --o-invert-hairline: rgba(255,255,255,.22);
  --o-invert-wash:     rgba(255,255,255,.06);

  /* #2563eb is too dark to read on this ground, so the accent lifts a step
     and the text ON it flips to near-black. */
  --o-signal:    #60a5fa;
  --o-signal-deep: #93c5fd;
  --o-on-signal:   #0b1220;
  --o-signal-tint: rgba(96, 165, 250, .14);
  --o-signal-edge: #3b6fb8;

  --o-water:  #60a5fa;
  --o-moss:   #4ade80;
  --o-brick:  #f87171;
  --o-heath:  #c4b5fd;
  --o-sand:   #fbbf24;

  /* The traffic light stays a traffic light — brightened so each step is
     still distinguishable against a dark card. */
  --o-grade-outstanding: #22c55e;
  --o-grade-good:    #fbbf24;
  --o-grade-ri:      #fb923c;
  --o-grade-inadequate:  #f87171;
  --o-grade-none:    #60a5fa;

  --o-lift1: 0 1px 3px rgba(0,0,0,.5);
  --o-lift2: 0 4px 14px rgba(0,0,0,.55);
  --o-lift3: 0 8px 28px rgba(0,0,0,.65);
  color-scheme: dark;
}

/* ── 3 · Base ────────────────────────────────────────────────────────────── */

.o {
  font-family: var(--o-sans);
  font-size: var(--o-t-body);
  line-height: 1.6;
  color: var(--o-ink);
  background: var(--o-paper);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-feature-settings: 'cv05' 1, 'cv11' 1;  /* single-storey l, humanist a */
}

.o *, .o *::before, .o *::after { box-sizing: border-box; }

/* The masthead is sticky, so anything scrolled to by an anchor (or by an
   assistive/automation "scroll into view") would otherwise land underneath it. */
html { scroll-padding-top: calc(var(--o-header-h) + 16px); }

.o ::selection { background: var(--o-signal-tint); color: var(--o-signal-deep); }

/* One focus treatment everywhere — an ochre bracket, never the browser blue. */
.o :focus-visible {
  outline: 2px solid var(--o-signal);
  outline-offset: 2px;
  border-radius: var(--o-r1);
}

/* ── 4 · Typography ──────────────────────────────────────────────────────── */

.o-display, .o-h1, .o-h2, .o-h3 {
  font-family: var(--o-serif);
  font-weight: 600;
  line-height: 1.04;
  letter-spacing: -0.022em;
  color: var(--o-ink);
  text-wrap: balance;
}

.o-display { font-size: var(--o-t-display); line-height: .98; letter-spacing: -0.032em; }
.o-h1      { font-size: var(--o-t-h1); }
.o-h2      { font-size: var(--o-t-h2); line-height: 1.1; letter-spacing: -0.018em; }
.o-h3      { font-size: var(--o-t-h3); line-height: 1.2; letter-spacing: -0.012em; }

.o-lede {
  font-size: var(--o-t-lede);
  line-height: 1.5;
  color: var(--o-ink-soft);
  max-width: 54ch;
  text-wrap: pretty;
}

/* Figures are the product. They get the mono, tabular, always. */
.o-fig {
  font-family: var(--o-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'zero' 1;
  letter-spacing: -0.02em;
}

.o-prose { max-width: var(--o-measure); }
.o-prose p { margin: 0 0 var(--o-s4); text-wrap: pretty; }
.o-prose > :last-child { margin-bottom: 0; }
.o-prose h2 { font: 600 var(--o-t-h2)/1.1 var(--o-serif); letter-spacing: -.018em;
              margin: var(--o-s7) 0 var(--o-s3); }
.o-prose h3 { font: 600 var(--o-t-h3)/1.2 var(--o-serif); margin: var(--o-s6) 0 var(--o-s2); }
.o-prose ul, .o-prose ol { margin: 0 0 var(--o-s4); padding-left: 1.15em; }
.o-prose li { margin: var(--o-s1) 0; }
.o-prose li::marker { color: var(--o-signal); }
.o-prose a { color: var(--o-signal); text-decoration: underline;
             text-underline-offset: .18em; text-decoration-thickness: 1px; }
.o-prose a:hover { text-decoration-thickness: 2px; }
.o-prose strong { font-weight: 600; color: var(--o-ink); }

/* ── 5 · Layout ──────────────────────────────────────────────────────────── */

.o-wrap   { width: 100%; max-width: var(--o-container); margin-inline: auto; padding-inline: var(--o-gutter); }
.o-wrap-mid    { max-width: var(--o-mid); }
.o-wrap-narrow { max-width: var(--o-narrow); }

.o-section { padding-block: clamp(var(--o-s7), 7vw, var(--o-s9)); }
.o-section-tight { padding-block: clamp(var(--o-s6), 5vw, var(--o-s7)); }

/* The field grid — 12 columns, but the useful part is the named spans below.
   Editorial asymmetry (7/5, 8/4) rather than everything sitting in halves. */
.o-grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: var(--o-s5); }
.o-col-4  { grid-column: span 4; }
.o-col-5  { grid-column: span 5; }
.o-col-6  { grid-column: span 6; }
.o-col-7  { grid-column: span 7; }
.o-col-8  { grid-column: span 8; }
.o-col-12 { grid-column: span 12; }

@media (max-width: 860px) {
  .o-grid > [class*="o-col-"] { grid-column: span 12; }
}

/* Auto-fitting card bed — the workhorse for school/area lists. */
.o-bed {
  display: grid;
  gap: var(--o-s4);
  grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr));
}

/* ── 6 · Components ──────────────────────────────────────────────────────── */

/* Eyebrow — a mono legend label with a leading tick, like a map key.
   Used above every section heading; it is the system's most repeated signature
   and the reason a page reads as one document rather than a stack of widgets. */
.o-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--o-t-xs);
  font-weight: 700;
  letter-spacing: .01em;
  color: var(--o-signal);
  background: var(--o-signal-tint);
  padding: 3px 11px;
  border-radius: var(--o-r-pill);
  margin-bottom: var(--o-s3);
}

/* Rule — a hairline with an ochre lead segment, the "north mark". */
.o-rule {
  border: 0;
  height: 1px;
  background: linear-gradient(to right, var(--o-signal) 0 28px, var(--o-rule) 28px);
  margin: var(--o-s5) 0;
}
.o-rule-soft { background: var(--o-rule-soft); }

/* Buttons ---------------------------------------------------------------- */
.o-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--o-s2);
  padding: 13px 22px;
  border: 1px solid transparent;
  border-radius: var(--o-r2);
  font-family: var(--o-sans);
  font-size: var(--o-t-sm);
  font-weight: 600;
  letter-spacing: -.005em;
  line-height: 1;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  transition: background var(--o-dur1) var(--o-ease),
              border-color var(--o-dur1) var(--o-ease),
              color var(--o-dur1) var(--o-ease),
              transform var(--o-dur1) var(--o-ease),
              box-shadow var(--o-dur1) var(--o-ease);
}
.o-btn:active { transform: translateY(1px); }

.o-btn-primary {
  background: linear-gradient(135deg, var(--o-ink-soft), var(--o-signal));
  color: var(--o-on-signal);
  box-shadow: var(--o-lift1);
}
.o-btn-primary:hover { background: linear-gradient(135deg, var(--o-signal), var(--o-signal-deep)); box-shadow: var(--o-lift2); }

.o-btn-secondary {
  background: var(--o-surface);
  border-color: var(--o-rule);
  color: var(--o-ink);
  box-shadow: var(--o-lift1);
}
.o-btn-secondary:hover { border-color: var(--o-ink-soft); box-shadow: var(--o-lift2); }

.o-btn-ghost { background: transparent; color: var(--o-ink); border-color: transparent; }
.o-btn-ghost:hover { background: var(--o-sunken); }

.o-btn-sm { padding: 9px 14px; font-size: var(--o-t-xs); }

/* A link that behaves like a map reference: arrow slides on hover. */
.o-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--o-signal);
  font-weight: 600;
  font-size: var(--o-t-sm);
  text-decoration: none;
}
.o-link::after {
  content: '→';
  transition: transform var(--o-dur1) var(--o-ease);
}
.o-link:hover::after { transform: translateX(4px); }

/* Cards ------------------------------------------------------------------ */
.o-card {
  position: relative;
  background: var(--o-surface);
  border: 1px solid var(--o-rule);
  border-radius: var(--o-r2);
  padding: var(--o-s5);
  transition: border-color var(--o-dur2) var(--o-ease),
              transform var(--o-dur2) var(--o-ease),
              box-shadow var(--o-dur2) var(--o-ease);
}
.o-card-link { display: block; text-decoration: none; color: inherit; }
.o-card-link:hover {
  border-color: var(--o-signal-edge);
  transform: translateY(-2px);
  box-shadow: var(--o-lift2);
}

/* Corner ticks — survey crop marks. Drawn only on hover so the resting page
   stays quiet, which is the whole trick: the detail rewards attention rather
   than demanding it. */
.o-card-ticked::before,
.o-card-ticked::after {
  content: '';
  position: absolute;
  width: 9px; height: 9px;
  border: 1px solid var(--o-signal);
  opacity: 0;
  transition: opacity var(--o-dur2) var(--o-ease);
  pointer-events: none;
}
.o-card-ticked::before { top: -1px; left: -1px; border-width: 1px 0 0 1px; }
.o-card-ticked::after  { bottom: -1px; right: -1px; border-width: 0 1px 1px 0; }
.o-card-ticked:hover::before,
.o-card-ticked:hover::after { opacity: 1; }

/* Stat — a mono figure over a letterspaced legend. The site's data is its
   whole claim to authority, so figures are set like survey markers. */
.o-stat { display: flex; flex-direction: column; gap: 2px; }
.o-stat-value {
  font-family: var(--o-mono);
  font-variant-numeric: tabular-nums;
  font-size: clamp(1.5rem, 1.1rem + 1.5vw, 2.05rem);
  font-weight: 700;
  letter-spacing: -.02em;
  line-height: 1;
  color: var(--o-ink);
}
.o-stat-label {
  font-size: var(--o-t-xs);
  font-weight: 500;
  color: var(--o-muted);
}
.o-stat-sub { font-size: var(--o-t-xs); color: var(--o-muted); }

/* A row of stats separated by hairlines — the title block of a survey sheet. */
.o-stat-band {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--o-s5);
  border-top: 1px solid var(--o-rule);
  border-bottom: 1px solid var(--o-rule);
  padding-block: var(--o-s5);
}
.o-stat-band > * + * {
  border-left: 1px solid var(--o-rule-soft);
  padding-left: var(--o-s5);
}
@media (max-width: 620px) {
  .o-stat-band > * + * { border-left: 0; padding-left: 0; }
}

/* Grade marks — inspection outcomes. A left ink bar rather than a filled pill:
   filled colour blocks at this density turned the old pages into confetti. */
.o-grade {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 9px 3px 8px;
  border-left: 3px solid var(--o-grade-none);
  background: var(--o-sunken);
  border-radius: 0 var(--o-r1) var(--o-r1) 0;
  font-family: var(--o-mono);
  font-size: var(--o-t-micro);
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--o-ink-soft);
  white-space: nowrap;
}
.o-grade-outstanding { border-left-color: var(--o-grade-outstanding); }
.o-grade-good        { border-left-color: var(--o-grade-good); }
.o-grade-ri          { border-left-color: var(--o-grade-ri); }
.o-grade-inadequate  { border-left-color: var(--o-grade-inadequate); }

/* Chips / filters -------------------------------------------------------- */
.o-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 13px;
  border: 1px solid var(--o-rule);
  border-radius: var(--o-r-pill);
  background: var(--o-surface);
  color: var(--o-ink-soft);
  font-family: var(--o-sans);
  font-size: var(--o-t-xs);
  font-weight: 500;
  cursor: pointer;
  text-decoration: none;
  transition: border-color var(--o-dur1) var(--o-ease),
              background var(--o-dur1) var(--o-ease),
              color var(--o-dur1) var(--o-ease);
}
.o-chip:hover { border-color: var(--o-signal-edge); color: var(--o-ink); }
.o-chip[aria-pressed="true"], .o-chip.is-on {
  background: var(--o-ink);
  border-color: var(--o-ink);
  color: var(--o-paper);
}

/* Form field — the postcode box is the single most important control on the
   site, so the field style is built for it first. */
.o-field {
  display: flex;
  align-items: center;
  gap: var(--o-s2);
  background: var(--o-surface);
  border: 1px solid var(--o-rule);
  border-radius: var(--o-r2);
  padding: 6px 6px 6px var(--o-s4);
  box-shadow: var(--o-lift1);
  transition: border-color var(--o-dur1) var(--o-ease), box-shadow var(--o-dur1) var(--o-ease);
}
.o-field:focus-within {
  border-color: var(--o-signal);
  box-shadow: var(--o-lift2), 0 0 0 3px var(--o-signal-tint);
}
.o-field input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  outline: none;
  color: var(--o-ink);
  font-family: var(--o-sans);
  font-size: 16px;   /* ≥16px or iOS Safari zooms on focus */
  padding: 12px 0;
}
/* --o-muted, not --o-faint: a placeholder is text a visitor has to read, so it
   has to clear 4.5:1. --o-faint measures 2.74:1 on paper and is for marks. */
/* The wrapper already renders the focus state (border + ring), so the input's
   own focus ring would draw a second rectangle inside the first. */
.o-field input:focus-visible { outline: none; }
.o-field input::placeholder { color: var(--o-muted); }
.o-field.is-error { border-color: var(--o-brick); }

/* Note / caveat block. This product's credibility rests on saying what the
   data cannot tell you, so the caveat has a real style rather than being
   grey small print. */
.o-note {
  border-left: 2px solid var(--o-signal-edge);
  background: var(--o-signal-tint);
  padding: var(--o-s3) var(--o-s4);
  border-radius: 0 var(--o-r2) var(--o-r2) 0;
  font-size: var(--o-t-sm);
  color: var(--o-ink-soft);
}
.o-source { font-size: var(--o-t-xs); color: var(--o-muted); }

/* Tables — data first: hairlines, tabular figures, no zebra shading. */
.o-table { width: 100%; border-collapse: collapse; font-size: var(--o-t-sm); }
.o-table th {
  text-align: left;
  font-size: var(--o-t-xs);
  font-weight: 600;
  color: var(--o-muted);
  padding: var(--o-s2) var(--o-s3);
  border-bottom: 1px solid var(--o-rule);
  white-space: nowrap;
}
.o-table td {
  padding: var(--o-s3);
  border-bottom: 1px solid var(--o-rule-soft);
  vertical-align: top;
}
.o-table tbody tr { transition: background var(--o-dur1) var(--o-ease); }
.o-table tbody tr:hover { background: var(--o-sunken); }
.o-table .num { font-family: var(--o-mono); font-variant-numeric: tabular-nums; text-align: right; }
.o-table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ── 7 · Motion ──────────────────────────────────────────────────────────── */

/* Ink-rise: the one entrance animation. Content settles down onto the page
   rather than flying in. ordnance.js adds .is-in via IntersectionObserver.

   Gated behind .o-js, which the inline head snippet sets. Without that gate a
   visitor (or a crawler) with JS disabled would get a page of permanently
   invisible content — an unacceptable failure mode on pages whose entire
   purpose is organic search. Default is visible; JS opts in to animating. */
.o-rise { opacity: 1; transform: none; }
.o-js .o-rise {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity var(--o-dur3) var(--o-ease), transform var(--o-dur3) var(--o-ease);
}
.o-js .o-rise.is-in { opacity: 1; transform: none; }

/* Scroll lock while the mobile nav is open. */
.o-nav-locked, .o-nav-locked body { overflow: hidden; }

/* Stagger children of a revealed group. */
.o-stagger > * { transition-delay: calc(var(--o-i, 0) * 60ms); }

/* Draw-in: a rule that draws itself left-to-right rather than fading. */
@keyframes o-draw { from { stroke-dashoffset: var(--o-len, 1000); } to { stroke-dashoffset: 0; } }
.o-draw path { stroke-dasharray: var(--o-len, 1000); animation: o-draw var(--o-dur4) var(--o-ease) forwards; }

@keyframes o-rule-draw { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.o-rule-draw { transform-origin: left; animation: o-rule-draw var(--o-dur3) var(--o-ease) forwards; }

/* Underline that grows from the left on hover — used for nav and card titles. */
.o-underline {
  background-image: linear-gradient(var(--o-signal), var(--o-signal));
  background-repeat: no-repeat;
  background-size: 0% 1px;
  background-position: 0 100%;
  transition: background-size var(--o-dur2) var(--o-ease);
  text-decoration: none;
}
.o-underline:hover, a:hover > .o-underline { background-size: 100% 1px; }

/* Cross-page continuity. Progressive enhancement — browsers without it just
   navigate normally, which is exactly the fallback we want on a Flask site
   that must keep working with JS off. */
@view-transition { navigation: auto; }

/* Global, not scoped to .o — the content pages style <body> directly rather
   than carrying the .o class, and a reduced-motion request must be honoured on
   every page regardless of which shell it uses. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  .o-js .o-rise { opacity: 1; transform: none; }
  @view-transition { navigation: none; }
}

/* ── 8 · Utilities ───────────────────────────────────────────────────────── */

.o-sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.o-stack   { display: flex; flex-direction: column; }
.o-row     { display: flex; align-items: center; }
.o-wrapped { flex-wrap: wrap; }
.o-gap2 { gap: var(--o-s2); } .o-gap3 { gap: var(--o-s3); }
.o-gap4 { gap: var(--o-s4); } .o-gap5 { gap: var(--o-s5); }
.o-mt4 { margin-top: var(--o-s4); } .o-mt5 { margin-top: var(--o-s5); }
.o-mt6 { margin-top: var(--o-s6); } .o-mt7 { margin-top: var(--o-s7); }
.o-muted { color: var(--o-muted); }
.o-sm { font-size: var(--o-t-sm); }
.o-xs { font-size: var(--o-t-xs); }
.o-center { text-align: center; }
.o-relative { position: relative; }

/* ═══════════════════════════════════════════════════════════════════════════
   9 · Site chrome — masthead, nav, footer
   Styles for _o_masthead.html and _o_footer.html.
   ═══════════════════════════════════════════════════════════════════════════ */

.o-mast {
  position: sticky;
  top: 0;
  z-index: 60;
  background: var(--o-mast-bg);
  color: var(--o-mast-fg);
  transition: box-shadow var(--o-dur2) var(--o-ease);
}
/* Flush with the page at rest; a hairline appears once you leave the top.
   ordnance.js toggles .is-stuck. */
.o-mast.is-stuck { box-shadow: 0 2px 10px rgba(15,42,68,.25); }

/* color-mix is well supported now, but if it is not understood the whole
   background declaration is dropped and the bar goes transparent over
   scrolling text. This opaque fallback is declared first so it survives. */
.o-mast-inner {
  max-width: var(--o-container);
  margin-inline: auto;
  padding: 0 var(--o-gutter);
  height: var(--o-header-h);
  display: flex;
  align-items: center;
  gap: var(--o-s5);
}

.o-brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  text-decoration: none;
  color: var(--o-mast-fg);
  margin-right: auto;
}
.o-brand-mark { flex: none; transition: transform var(--o-dur3) var(--o-ease); }
.o-brand:hover .o-brand-mark { transform: rotate(-18deg); }

.o-wordmark {
  font-family: var(--o-serif);
  font-weight: 600;
  font-size: 1.15rem;
  letter-spacing: -.028em;
  white-space: nowrap;
}

.o-nav { display: flex; align-items: center; gap: var(--o-s1); }

.o-nav-link {
  position: relative;
  padding: 8px 11px;
  border-radius: var(--o-r1);
  color: var(--o-mast-dim);
  text-decoration: none;
  font-size: var(--o-t-sm);
  font-weight: 500;
  white-space: nowrap;
  transition: color var(--o-dur1) var(--o-ease);
}
.o-nav-link:hover { color: var(--o-mast-fg); }

/* The current section is marked with an ochre tick beneath, drawn from the
   left — the same gesture as .o-underline, so navigation and content share
   one hover language. */
.o-nav-link::after {
  content: '';
  position: absolute;
  left: 11px; right: 11px; bottom: 3px;
  height: 1.5px;
  background: #7dd3fc;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--o-dur2) var(--o-ease);
}
.o-nav-link:hover::after,
.o-nav-link.is-current::after { transform: scaleX(1); }
.o-nav-link.is-current { color: var(--o-mast-fg); font-weight: 600; }

.o-nav-sep {
  width: 1px; height: 20px;
  background: rgba(255,255,255,.22);
  margin-inline: var(--o-s2);
}

.o-nav-shortlist {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  border: 1px solid rgba(255,255,255,.28);
  border-radius: var(--o-r-pill);
  color: var(--o-mast-fg);
  text-decoration: none;
  font-size: var(--o-t-xs);
  font-weight: 600;
  white-space: nowrap;
  transition: border-color var(--o-dur1) var(--o-ease), background var(--o-dur1) var(--o-ease);
}
.o-nav-shortlist:hover { border-color: #fff; background: rgba(255,255,255,.12); }

.o-nav-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 17px; height: 17px; padding: 0 5px;
  border-radius: var(--o-r-pill);
  background: var(--o-signal);
  color: var(--o-on-signal);
  font-family: var(--o-mono);
  font-size: 10px; font-weight: 600;
}
.o-nav-count[hidden] { display: none !important; }

.o-theme-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px;
  border: 1px solid transparent;
  border-radius: var(--o-r2);
  background: none;
  color: var(--o-mast-dim);
  cursor: pointer;
  transition: color var(--o-dur1) var(--o-ease), background var(--o-dur1) var(--o-ease);
}
.o-theme-btn:hover { color: var(--o-mast-fg); background: rgba(255,255,255,.12); }

/* Which glyph shows is pure CSS, so the button is correct on first paint
   rather than after ordnance.js runs. Offer the theme you would switch TO. */
.o-ico-sun  { display: none; }
.o-ico-moon { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .o-ico-sun  { display: block; }
  :root:not([data-theme="light"]) .o-ico-moon { display: none; }
}
:root[data-theme="dark"]  .o-ico-sun  { display: block; }
:root[data-theme="dark"]  .o-ico-moon { display: none; }
:root[data-theme="light"] .o-ico-sun  { display: none; }
:root[data-theme="light"] .o-ico-moon { display: block; }

/* Burger — two rules that cross into an X. */
.o-mast-burger {
  display: none;
  width: 38px; height: 38px;
  border: 1px solid rgba(255,255,255,.3);
  border-radius: var(--o-r2);
  background: transparent;
  cursor: pointer;
  position: relative;
  flex: none;
}
.o-mast-burger span {
  position: absolute;
  left: 10px; right: 10px;
  height: 1.5px;
  background: var(--o-mast-fg);
  transition: transform var(--o-dur2) var(--o-ease), opacity var(--o-dur1) var(--o-ease);
}
.o-mast-burger span:nth-child(1) { top: 14px; }
.o-mast-burger span:nth-child(2) { bottom: 14px; }
.o-mast-burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(4.5px) rotate(45deg); }
.o-mast-burger[aria-expanded="true"] span:nth-child(2) { transform: translateY(-4.5px) rotate(-45deg); }

@media (max-width: 900px) {
  .o-mast-burger { display: block; }
  .o-nav {
    position: fixed;
    inset: var(--o-header-h) 0 auto 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: var(--o-s4) var(--o-gutter) var(--o-s6);
    background: var(--o-mast-bg);
    box-shadow: var(--o-lift3);
    transform: translateY(-8px);
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--o-dur2) var(--o-ease),
                transform var(--o-dur2) var(--o-ease),
                visibility var(--o-dur2);
    max-height: calc(100dvh - var(--o-header-h));
    overflow-y: auto;
  }
  .o-nav.is-open { transform: none; opacity: 1; visibility: visible; }
  .o-nav-link {
    padding: 14px 0;
    font-size: 1.05rem;
    border-bottom: 1px solid rgba(255,255,255,.14);
    border-radius: 0;
  }
  .o-nav-link::after { left: 0; right: auto; width: 22px; bottom: 8px; }
  .o-nav-sep { display: none; }
  .o-nav-shortlist { margin-top: var(--o-s4); justify-content: center; padding: 13px; font-size: var(--o-t-sm); }
}

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

.o-foot {
  margin-top: var(--o-s9);
  border-top: 1px solid var(--o-rule);
  background: var(--o-sunken);
  padding-block: var(--o-s7) var(--o-s5);
}
.o-foot-top {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--o-s3) var(--o-s5);
  padding-bottom: var(--o-s6);
}
.o-foot-brand { display: inline-flex; align-items: center; gap: 9px; color: var(--o-ink); }
.o-foot-line {
  margin: 0;
  max-width: 46ch;
  font-size: var(--o-t-sm);
  color: var(--o-muted);
}

.o-foot-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--o-s5) var(--o-s6);
  padding-block: var(--o-s6);
  border-top: 1px solid var(--o-rule);
}
.o-foot-col { display: flex; flex-direction: column; align-items: flex-start; gap: 9px; }
.o-foot-head {
  margin: 0 0 var(--o-s1);
  font-size: var(--o-t-sm);
  font-weight: 700;
  color: var(--o-ink);
}
.o-foot-col a {
  color: var(--o-muted);
  text-decoration: none;
  font-size: var(--o-t-sm);
  transition: color var(--o-dur1) var(--o-ease);
}
.o-foot-col a:hover { color: var(--o-signal); }
.o-foot-sources { margin: 0; font-size: var(--o-t-xs); color: var(--o-muted); line-height: 1.55; }

.o-foot-bottom {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--o-s3);
  padding-top: var(--o-s5);
  border-top: 1px solid var(--o-rule);
}
.o-foot-note { margin: 0; font-size: var(--o-t-xs); color: var(--o-muted); max-width: 62ch; }
