
/* Instrument Serif, served from this domain rather than from Google.
   Copyright 2022 The Instrument Serif Project Authors, SIL Open Font License 1.1.
   The licence travels with it at /swim/instrument-serif-OFL.txt, as the OFL requires.

   Two files, with Google's own unicode-ranges kept: a visitor reading English
   pages downloads the 15KB latin file and never touches the 8KB extended one.
   font-display:swap so the page is readable in the system serif immediately. */
@font-face{font-family:"Instrument Serif";font-style:normal;font-weight:400;
  font-display:swap;src:url(/swim/instrument-serif-latin.woff2) 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:"Instrument Serif";font-style:normal;font-weight:400;
  font-display:swap;src:url(/swim/instrument-serif-latin-ext.woff2) 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}

/* Can I Swim? — the whole visual system in one file.
   Tokens first, then structure, then components. Dark mode redefines only the
   tokens, so nothing below has to know which mode it is in. */

:root{
  /* paper and ink — warmed towards sand, so it reads coastal not clinical */
  --bg:#f8f6f1;
  --bg-sunk:#f0ece3;
  --card:#fff;
  --ink:#101a1c;
  --ink-soft:#3c4b4d;
  --dim:#5b6b6d;
  /* Small text on the coloured verdict backgrounds. --dim measures 4.49 on
     --avoid-bg in light and 3.96 on --caution-bg in dark, both under 4.5, and
     it is used at 11.5px. Measured, not guessed. */
  --dim-strong:#45585a;
  --line:#e5e1d7;
  --line-strong:#d5cfc1;

  /* the sea */
  --brand:#0d4650;
  --brand-bright:#11707d;
  --brand-ink:#f4fbfa;
  /* The ink for a filter chip that is switched OFF. It has to sit on --card in
     both themes, and the two themes want opposite ends of the brand for that:
     the deep sea reads best on white, and only the bright one is legible on the
     dark card. Written as its own token so the two never drift. */
  --feature-ink:var(--brand);
  /* The weather glyphs in the week strip. Measured against the card they sit
     on, in both themes: the light values were picked for white and were 2.65:1
     (sun) and 3.11:1 (snow) — decoration passing as information. */
  --wsym-sun:#a06300;
  --wsym-rain:#1f78b4;
  --wsym-snow:#2a7099;

  /* status. These are not picked by eye: each was searched for the widest
     separation still achievable under deuteranopia and protanopia while keeping
     the conventional hue and clearing AA on both white and its own tint. The
     worst pair improves from 0.172 to 0.259 against the set they replace, and
     every one got darker and richer in the process. */
  --ok:#0f5635;        --ok-bg:#def3ea;   --ok-line:#95d8bd;
  --caution:#9e5c00;   --caution-bg:#fdf2e0; --caution-line:#f8d49a;
  --avoid:#680a04;     --avoid-bg:#fae1e0; --avoid-line:#eea09a;
  --advised:#a03113;   --advised-bg:#fdece3; --advised-line:#f0b394;
  --unknown:#4c675e;   --unknown-bg:#d5e2e0; --unknown-line:#aec7c2;

  /* waterfalls are the other half of the site and get their own accent, so the
     two halves feel distinct without leaving the family */
  --fall:#1d5c8a;      --fall-bg:#e2eff8; --fall-line:#a1cdea;

  /* The four steps of the flow scale, on the maps.
     A SCALE HAS TO RUN ONE WAY. The map used to build this out of --fall for
     the top, --fall-line for the bottom and two hardcoded blues in between —
     which reads correctly on a pale map, where "more water" is "more ink", and
     falls apart in the dark, where --fall becomes light and --fall-line becomes
     a dark navy: spate came out light, flowing-well darker, modest LIGHTER
     again, and likely-low the darkest dot on the map. Four steps that meant
     nothing in the order they appeared.
     Light: darkest is strongest, on a pale ground.
     Dark below: brightest is strongest, on a dark one. Same idea, inverted,
     which is the only way it stays a scale in both. */
  /* DARKENED ACROSS THE BOTTOM HALF. Jay, on the waterfalls map: "the dots
     need to be bright, or you can hardly see the waterfalls." The two pale
     steps were mixed from the same ramp as the top two and were nearly the
     value of the OpenStreetMap land behind them, so most of 645 falls
     disappeared into Cornwall. The scale still runs pale-to-dark in the same
     order and the same four steps; it just starts from somewhere you can see. */
  --flow-4:#123f61;    /* in spate, flowing strongly */
  --flow-3:#2b6f9e;    /* flowing well */
  --flow-2:#4f95bd;    /* modest */
  --flow-1:#79b4d8;    /* likely low */
  /* NOT part of the scale. No rain reading for this fall is not the same fact
     as a low one, and the saved map was painting it in the likely-low colour —
     saying "there is barely any water" about a waterfall nobody had measured.
     Grey, and off the blue ramp entirely. */
  --flow-none:#6b7a86;

  --focus:#0b5fd6;
  /* The masthead and footer are dark teal, where the blue ring above measures
     1.8:1 — WCAG asks 3:1 of a focus indicator. This one is 5.7:1 on both. */
  --focus-brand:#8cc4ff;

  /* elevation — two steps only, used consistently */
  --lift:0 1px 2px rgba(16,26,28,.05), 0 1px 1px rgba(16,26,28,.04);
  --lift-2:0 2px 4px rgba(16,26,28,.05), 0 12px 28px -12px rgba(16,26,28,.18);

  --r-s:10px; --r-m:14px; --r-l:20px;
  --wrap:1120px; --read:760px;
  --serif:"Instrument Serif", Georgia, "Times New Roman", serif;
  --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
}

@media (prefers-color-scheme:dark){
  :root{
    --bg:#0c1214;
    --bg-sunk:#080d0e;
    --card:#141d1f;
    --ink:#e9eeec;
    --ink-soft:#c2cdcb;
    --dim:#93a3a3;
    --dim-strong:#adbcbc;
    --line:#222e30;
    --line-strong:#2d3b3d;

    --brand:#0e3941;
    --brand-bright:#5ec8d3;
    --brand-ink:#eafcff;
    /* 1.37:1 before this. --brand is #0e3941 in dark mode and the card behind
       it is #141d1f, so the Lifeguarded and Blue Flag chips were drawn in a
       colour all but identical to their own background — label, count and
       lifebuoy invisible until pressed. The same fault already fixed for
       .btn.save.on and .ordedit, and fixed the same way. 8.7:1 now. */
    --feature-ink:var(--brand-bright);
    /* Light glyph colours on a dark card are the same failure the other way
       round, so these are their own values rather than the same three hoping
       to suit both. 9:1, 7.8:1 and 9:1. */
    --wsym-sun:#f0b040;
    --wsym-rain:#6fb6e8;
    --wsym-snow:#7fc4e8;

    /* Dark mode is measured too, and the measurement says something worth
       recording: on a dark ground, status colour CANNOT carry this signal. Every
       one has to be light enough to read, which compresses exactly the
       differences colour blindness removes — the best natural set scores 0.110
       against light mode's 0.259, and chasing a higher number only produces pale
       mints and washed pinks that read as decoration rather than warning. So
       these are chosen to look right, and the map leans on size, outline and
       dash instead of hue. */
    --ok:#5fe0a4;        --ok-bg:#0e3f2e;   --ok-line:#106947;
    --caution:#f0b429;   --caution-bg:#503d17; --caution-line:#90641a;
    --avoid:#ff8f85;     --avoid-bg:#4a1a17; --avoid-line:#83211a;
    --advised:#ffa877;   --advised-bg:#4a2a17; --advised-line:#8a4d26;
    --unknown:#93a8a3;   --unknown-bg:#2a3b38; --unknown-line:#456159;

    --fall:#57a0d5;      --fall-bg:#153345; --fall-line:#205b81;

    /* Inverted, so the scale still runs one way. Measured against the dark
       basemap (#1b2226-ish) rather than the card: every step is a marker fill,
       and --flow-1 has to stay findable without competing with --flow-4. */
    --flow-4:#9ad3f7;
    --flow-3:#57a0d5;
    --flow-2:#3d7ba6;
    --flow-1:#2f5c78;
    --flow-none:#71828d;

    --focus:#8cc4ff;
    --focus-brand:#8cc4ff;
    --lift:0 1px 2px rgba(0,0,0,.4);
    --lift-2:0 2px 4px rgba(0,0,0,.35), 0 16px 32px -14px rgba(0,0,0,.6);
  }
}

*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth;
  /* The masthead is sticky from 600px up, so a jump to an anchor put the thing
     you jumped to underneath it — the heading hidden behind the header and the
     reader looking at the paragraph after it, wondering where the link went.
     --headh is the header's measured height, published by core.js, so this
     follows a two-row header as well as a one-row one; the fallback is the
     desktop height for the moment before the measurement lands. Matters now
     that /saved/ links to /how-it-works/#app. */
  scroll-padding-top:calc(var(--headh, 62px) + 14px)}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}
  *{animation-duration:.01ms !important;transition-duration:.01ms !important}}

body{
  margin:0;background:var(--bg);color:var(--ink);
  font:400 16.5px/1.6 var(--sans);
  -webkit-font-smoothing:antialiased;
  padding-bottom:env(safe-area-inset-bottom);
}
img{max-width:100%}
a{color:var(--brand-bright);text-underline-offset:2px}
a:hover{color:var(--brand)}
@media (prefers-color-scheme:dark){a:hover{color:#8fe3ec}}
:focus-visible{outline:3px solid var(--focus);outline-offset:2px;border-radius:6px}
.masthead :focus-visible,.sitefoot :focus-visible{outline-color:var(--focus-brand)}

.wrap{max-width:var(--wrap);margin:0 auto;
  /* viewport-fit=cover is set in the head, so in landscape on a notched phone
     the safe area is ~44px and a flat 20px would put text under the camera. */
  padding-left:max(20px,env(safe-area-inset-left));
  padding-right:max(20px,env(safe-area-inset-right))}
.read{max-width:var(--read);margin:0 auto}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
/* The first tab stop on every page. Without it a keyboard or screen reader user
   walks the whole masthead before reaching the thing they came for. */
.skip{position:absolute;left:-9999px;top:0;z-index:1200;background:var(--card);color:var(--ink);
  border:1px solid var(--line-strong);border-radius:0 0 10px 0;padding:12px 18px;font-weight:650;
  text-decoration:none;box-shadow:var(--lift-2)}
.skip:focus{left:0}
#content:focus{outline:none}

/* ---------- header ------------------------------------------------------ */
/* The header carries the brand the way the footer does, so the page sits framed
   between two blocks of sea rather than fading off at the top. */
.masthead{
  position:sticky;top:0;z-index:1000;
  /* Solid first, translucent second. A browser too old for color-mix() drops
     that declaration and keeps this one; without it the background fell back to
     transparent while backdrop-filter — supported years earlier — kept working,
     so the header text sat unreadable over whatever scrolled beneath it. */
  background:var(--brand);
  background:color-mix(in srgb, var(--brand) 94%, transparent);
  backdrop-filter:saturate(1.4) blur(12px);
  -webkit-backdrop-filter:saturate(1.4) blur(12px);
  border-bottom:1px solid rgba(255,255,255,.14);
}
.masthead .inner{
  max-width:var(--wrap);margin:0 auto;padding:0 20px;
  display:flex;align-items:center;justify-content:space-between;gap:18px;min-height:62px;
}
.brand{
  display:inline-flex;align-items:center;gap:10px;text-decoration:none;color:#fff;
  font:400 22px/1 var(--serif);letter-spacing:.01em;white-space:nowrap;
}
.brand:hover{color:#fff}
.brand .mark{
  /* The wave on its own, no box behind it. Boxed, it read as an app icon parked
     in a bar; loose, it reads as a mark. The viewBox is cropped to the wave plus
     its stroke, so the glyph fills the space instead of floating in a square of
     nothing. The favicon keeps its box, because an icon needs a shape to sit in.
     Sized here, coloured by the header. */
  width:34px;height:16px;flex:none;display:block;overflow:visible}
.brand .mark path{stroke:#fff}
.masthead nav{display:flex;gap:4px;align-items:center;overflow-x:auto;scrollbar-width:none}
.masthead nav::-webkit-scrollbar{display:none}
.masthead nav a{
  color:#b9d3d5;text-decoration:none;font-size:14.5px;font-weight:500;
  padding:8px 11px;border-radius:999px;white-space:nowrap;
}
.masthead nav a:hover{color:#fff;background:rgba(255,255,255,.10)}

/* Under 600px the four links need 402px beside a 130px wordmark, so they were
   living in a 187px scroller with the scrollbar hidden: three of the site's four
   sections were invisible on every phone made. Give them their own row. The
   sticky search bar below follows via --headh rather than a fixed 62px. */
@media (max-width:599px){
  /* Two rows of links plus the wordmark is about 145px, and holding that at the
     top of an 812px phone for the whole scroll costs more than it gives. The
     header goes with the page and the search bar takes over the sticky job — so
     the sections are all there when you arrive, and the screen is yours after. */
  /* position:relative, not static, so the menu panel below can hang off it —
     and note that this also un-suppresses the z-index:1000 set at the top of
     .masthead, which static had been ignoring. That is wanted: the panel has to
     sit over the page, not under it. It still does not STICK, so .search{top:0}
     just below and the #content anchor jumps both stay correct. */
  .masthead{position:relative}
  /* The gutter the menu panel and the header padding both follow. 16px here,
     20px from 600 up, matching the page's own gutters at each width. */
  .masthead{--gut:16px}
  .masthead .inner{
    flex-direction:column;align-items:stretch;justify-content:center;
    gap:0;min-height:0;padding:7px 16px 5px;
  }
  .masthead nav{overflow-x:visible;flex-wrap:wrap;gap:2px;justify-content:flex-start}
  .masthead nav a{padding:9px 10px;font-size:14px}
  .brand{font-size:20px;align-self:flex-start;padding:3px 0 5px}
}
/* BOTH VALUES. An index page marks its own nav link aria-current="page"; every
   page beneath it marks the section link aria-current="true", which is correct
   markup and was styled by nothing — so on 1,793 of the 1,802 built pages the
   "you are here" highlight simply was not there, while a screen reader
   announced it perfectly well. */
.masthead nav a[aria-current]{color:#fff;background:rgba(255,255,255,.17)}
.masthead nav a[aria-current=false]{color:inherit;background:none}
/* The live count beside "Warnings".
   IT MUST NOT CHANGE THE WIDTH OF THE NAV. It used to be display:none until a
   page had loaded the beach snapshot, so it was there on the swimming list and
   gone on the waterfalls page — and every link in the bar slid sideways as you
   moved between them. It now always occupies the same box and only its contents
   appear, so the nav is nailed down whether the number is known or not. */
.navcount{
  display:inline-block;margin-left:6px;padding:1px 7px;border-radius:999px;
  background:var(--avoid);color:#fff;font-size:12px;font-weight:700;
  font-variant-numeric:tabular-nums;vertical-align:1px;
  min-width:calc(2ch + 14px);text-align:center;   /* 2 digits + the 7px padding either side */
}
/* NOT display:none — that is the reflow. Invisible, same footprint.
   The site-wide [hidden]{display:none!important} rule exempts .navcount for
   exactly this reason. */
.navcount[hidden]{visibility:hidden}
/* --avoid flips to a pale salmon in dark mode, and white on it is 2.2:1. The
   badge is a count of live warnings, so it has to stay legible: dark ink on the
   same salmon is 8.2:1 and keeps the colour meaning. */
@media (prefers-color-scheme:dark){.navcount{color:#2b0a07}}

/* Touch. Apple asks 44px, Android 48dp; the pills sat at 40 and the nav at 39.
   Scoped to coarse pointers so the desktop layout keeps its proportions. */
@media (pointer:coarse){
  .btn{min-height:44px}
  .pill{min-height:44px;padding-top:10px;padding-bottom:10px}
  .masthead nav a{min-height:44px;display:inline-flex;align-items:center}
  .brand{min-height:44px}
  /* Leaflet's own stylesheet is injected at runtime, so it lands after this one
     and wins any tie. Naming the control class takes the specificity to 0,3,1
     against its 0,2,1 — no !important needed. */
  .seenpick{min-height:44px}
  /* The wrap-instead-of-scroll rule stops at 599px, so a phone in landscape got
     a horizontal scroller with its scrollbar deliberately hidden — links with
     nothing to say they were there. Touch devices wrap at any width. */
  .masthead nav{overflow-x:visible;flex-wrap:wrap;row-gap:4px}
  /* The waterfall and area filters were built later than this block and never
     joined it: pills sat at 33px, the county rows at 24px and Clear filters at
     21px — the smallest things on the site were the ones doing the filtering. */
  .ffpill{min-height:44px;padding-top:10px;padding-bottom:10px}
  .ffalist label{min-height:44px;padding-top:8px;padding-bottom:8px}
  .ffareas>summary{min-height:44px;display:flex;align-items:center}
  .ffclear{min-height:44px;padding:0 2px}
  /* Disclosure toggles that open real content — the storm-overflow list, the
     report form, the embed snippet — were a single 23px line of text. */
  details>summary{min-height:44px;display:flex;align-items:center}
  /* Map furniture. Leaflet ships these at 26-30px. */
  .leaflet-touch .leaflet-control-layers-toggle{width:44px;height:44px;
    background-size:22px 22px}
  .leaflet-touch .leaflet-control-layers-expanded label{min-height:36px;
    display:flex;align-items:center;gap:7px}
  .leaflet-popup-close-button{width:38px !important;height:38px !important;
    font-size:22px !important;line-height:34px !important}
  .leaflet-popup-content a{display:inline-flex;align-items:center;min-height:34px}
  /* The footer is a list of links to the site's five sections — navigation, not
     a sentence, so the inline-link exemption does not cover it. */
  .sitefoot li a{display:inline-flex;align-items:center;min-height:44px}
  .leaflet-touch .leaflet-bar a.leaflet-control-zoom-in,
  .leaflet-touch .leaflet-bar a.leaflet-control-zoom-out{
    width:44px;height:44px;line-height:44px;font-size:20px;
  }
}

/* ---------- the phone menu ------------------------------------------------ */
/* Eight links wrapped to three rows and made the header 197px tall on a 320px
   phone — a quarter of the screen, before a word of the page. They are now
   behind a three-line button in the corner.
//
   IT IS A <details>, AND THE NAV IS ITS SIBLING RATHER THAN ITS CONTENT.
   Both halves are load-bearing.

   <details>, because the toggle then costs no JavaScript at all. core.js
   failing to arrive is a failure this page already carries a retry loop and a
   twelve-second watchdog for, and a menu wired up in core.js would leave a
   phone with no navigation on precisely the visits where the readings did not
   load either. Worse, the site-wide [hidden] rule is !important, so a
   script-driven panel that never got un-hidden could not be rescued by any
   later rule. Four of these eight sections appear nowhere else on the page.

   A SIBLING, because a browser hides a <details>'s own children through
   ::details-content, which no `display` rule can undo — a nav nested inside
   would still be a phantom box of unfocusable links when the desktop rule tried
   to force it open. Hung outside on `.burger:not([open]) ~ nav`, the panel is
   hidden by an ordinary rule that the media query simply stops applying above
   600px, so whatever hides it is also what shows it again, at any width, with
   no reload and nothing to keep in sync. */
.burger{display:none;flex:none;margin:0}
/* 881px, not 599px.
   Eight links need 686px of their own, and beside the 138px wordmark and the
   gutters that is 882px of viewport. Below that they had been living in the
   base rule's overflow-x:auto scroller with its scrollbar deliberately hidden —
   so at 885px the last three sections (Guides, Map, How it works) were simply
   not there, with nothing on screen to say a sideways drag would find them.
   That is every tablet and every half-width desktop window.
   The menu is the fix the site already has, so it is the one used, rather than
   a second small-screen layout to keep in step with this one. The BOUNDARY is
   measured: change a nav label and the number moves, so tools/build_swim_site.py
   asserts it. */
@media (max-width:970px){
  .burger{display:block}
  .burger>summary{
    min-width:44px;min-height:44px;padding:0 10px;border-radius:999px;
    display:flex;align-items:center;justify-content:center;gap:7px;
    color:#b9d3d5;cursor:pointer;list-style:none;
  }
  /* Both spellings: WebKit still uses the pseudo-element on older iOS. */
  .burger>summary::-webkit-details-marker{display:none}
  .burger>summary::marker{content:""}
  .burger[open]>summary,.burger>summary:hover{color:#fff;background:rgba(255,255,255,.12)}
  .burgerbars{display:block;position:relative;width:18px;height:2px;border-radius:2px;
    background:currentColor}
  .burgerbars::before,.burgerbars::after{content:"";position:absolute;left:0;
    width:18px;height:2px;border-radius:2px;background:currentColor}
  .burgerbars::before{top:-6px}
  .burgerbars::after{top:6px}

  /* Back to one row: wordmark left, button right. This undoes the column
     layout the block above sets, and only works because this block comes
     after it — same specificity, later wins. */
  .masthead .inner{
    flex-direction:row;align-items:center;justify-content:space-between;
    gap:10px;padding:6px var(--gut,20px);min-height:56px;
  }
  .brand{align-self:center;padding:0}

  /* Closed. One rule, and the 600px boundary is the only thing that lifts it. */
  .burger:not([open]) ~ nav{display:none}

  /* Open: a panel hanging off the header rather than pushing it taller, so the
     measured --headh the sticky search bar follows never changes underneath it.
     Pinned to .inner's own 16px gutter, not to the full-bleed masthead, so its
     right edge lines up with the button's. */
  .masthead nav{
    position:absolute;top:calc(100% + 6px);right:var(--gut,20px);left:auto;
    min-width:min(16rem,calc(100vw - 32px));max-width:calc(100vw - 32px);
    flex-direction:column;align-items:stretch;flex-wrap:nowrap;gap:2px;
    padding:8px;border-radius:14px;
    background:var(--brand);border:1px solid rgba(255,255,255,.18);
    box-shadow:var(--lift-2);z-index:1001;
    /* SCROLLABLE, OR THE BOTTOM OF THE MENU DOES NOT EXIST.
       Between 600 and 881px the masthead is still sticky, and this panel hangs
       off it at fixed viewport coordinates. Turn a phone on its side — 852x393
       — and the panel runs 62 to 524, so Map and How it works sit below a 393px
       fold at every scroll position, unreachable by scrolling and unreachable
       by keyboard. Capped to what is actually on screen, and it scrolls.
       dvh second so it wins where it is understood: on a phone the browser
       chrome moves, and 100vh is the tallest the viewport ever gets rather than
       the height it has now. */
    max-height:calc(100vh - 72px);
    max-height:calc(100dvh - 72px);
    overflow-y:auto;overscroll-behavior:contain;
  }
  /* On a dark page the panel is teal on near-black: 1.51:1, so the edge is
     doing the work of saying where the panel stops. Give it more edge. Light
     mode does not need it — there the panel is a dark block on a pale page. */
  @media (prefers-color-scheme:dark){
    .masthead nav{border-color:rgba(255,255,255,.32)}
  }
  .masthead nav a{
    display:flex;align-items:center;justify-content:space-between;gap:10px;
    width:100%;padding:11px 13px;border-radius:10px;font-size:16px;
  }
}

/* ---------- page furniture ---------------------------------------------- */
main{display:block}
.page{padding:26px 0 70px}
h1{font:400 clamp(30px,4.4vw,44px)/1.08 var(--serif);margin:0 0 8px;letter-spacing:-.005em}
h2{font:650 21px/1.25 var(--sans);margin:34px 0 12px;letter-spacing:-.015em}
h3{font:650 16px/1.3 var(--sans);margin:22px 0 8px}
.sub{color:var(--dim);font-size:16px;margin:0 0 4px;max-width:62ch}
details>p.sub{margin-top:10px}
form.card{margin-top:12px}
.crumb{font-size:13px;color:var(--dim);margin:0 0 12px}
.crumb a{color:var(--dim);text-decoration:none}
.crumb a:hover{color:var(--brand-bright);text-decoration:underline}

.freshness{display:inline-flex;align-items:center;gap:7px;font-size:13px;color:var(--dim);
  margin-top:10px;background:var(--card);border:1px solid var(--line);
  padding:5px 11px;border-radius:999px;box-shadow:var(--lift)}
/* Neutral until something is known. It defaulted to green, so every page in the
   site showed a green dot beside the word "Checking" before anything had
   loaded — and stayed green if the script never ran at all. */
.dot{width:7px;height:7px;border-radius:50%;background:var(--unknown);flex:none;
  box-shadow:0 0 0 3px color-mix(in srgb, var(--ok) 18%, transparent)}
.dot.stale{background:var(--caution);box-shadow:0 0 0 3px color-mix(in srgb, var(--caution) 18%, transparent)}
.dot.fresh{background:var(--ok)}
.dot.bad{background:var(--avoid);box-shadow:0 0 0 3px color-mix(in srgb, var(--avoid) 18%, transparent)}

/* ---------- controls ----------------------------------------------------- */
button{font:inherit;cursor:pointer}
.btn{
  border:1px solid var(--line-strong);background:var(--card);color:var(--ink);
  border-radius:999px;padding:8px 14px;box-shadow:var(--lift);white-space:nowrap;
  display:inline-flex;align-items:center;gap:8px;font-size:14.5px;font-weight:550;
  transition:border-color .15s, transform .15s, box-shadow .15s;
}
.btn:hover{border-color:var(--brand-bright);box-shadow:var(--lift-2)}
.btn:active{transform:translateY(1px)}
.btn[aria-pressed=true]{background:var(--brand);color:var(--brand-ink);border-color:var(--brand)}

.search{position:sticky;top:var(--headh,62px);z-index:900;background:var(--bg);padding:7px 0;
  box-shadow:0 10px 12px -12px rgba(16,26,28,.28)}
/* Must come after the rule above, not with the rest of the small-screen header:
   same specificity, so whichever is written last wins. On a phone the masthead
   scrolls away and this takes the top itself. */
@media (max-width:599px){.search{top:0}}
/* "Near me" beside the box leaves it 169px wide, and the placeholder needs 247,
   so it read "Search a beach, lake,". Stack them and the box gets the full
   width — the wording is right, so it should be the layout that gives. */
@media (max-width:479px){
  /* Was nowrap, which left the field 143-169px against a placeholder needing
     247 — so it read "Search a beach, lake, ri". The comment above this block
     already said the wording was right and the layout should give, so let it:
     the box takes the full width and Near me sits under it.

     Note `.searchrow .field`, not `.field`. This block sits ABOVE the base
     `.field{flex:1}` a few lines down, and a media query adds no specificity —
     so a bare `.field` here loses to it on source order and does nothing at
     all. The old `min-width:0` on this line had been dead for that reason. */
  .searchrow{flex-wrap:wrap;gap:7px}
  .searchrow .field{flex:1 1 100%;min-width:0}
  .searchrow .btn{flex:1 1 auto;justify-content:center}
}
/* Surf is an attribute of a beach, not a kind of place, so it rides on the
   card's location line rather than getting a section of its own.
   DELIBERATELY NOT --fall. That blue is the waterfall's, on the same page, on
   the map pins and on the waterfall rows underneath — a blue chip on a beach
   card would make one colour mean two things within a screen of each other.
   The brand teal is not spoken for at this size. */
.surfchip{
  display:inline-block;margin-left:8px;padding:1px 7px;border-radius:999px;
  font-size:11px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;
  background:var(--card);color:var(--brand-bright);border:1px solid var(--line-strong);
  vertical-align:1px;
}
/* Waterfall rows in the search results. They deliberately do NOT look like a
   beach card with a missing verdict — the mark is an arrow rather than a tick
   or a cross, and the word where a verdict would be says what the thing is
   instead of how safe it is, because there is no safety reading for a
   waterfall and a blank there would be read as one. */
.fallrow .mark.fall{
  background:var(--fall-bg);color:var(--fall);border:1px solid var(--fall-line);
}
.bverdict.fall{color:var(--fall);font-weight:650}
#fallsresults{margin-top:18px}
#fallsresults h2{margin-top:0}

/* What the site covers, as links rather than a sentence: five facts a reader
   can act on, in the space one more clause of prose would have taken. */
/* These were 13.5px dim ink in a 1px --line box on an off-white page: thin,
   pale, and three ragged rows of them. They are the site's proposition, so they
   are allowed to carry some weight — a real number, a real label, and an edge
   you can see. */
/* THE ONE LIVE LINE ON THE FRONT DOOR.
   Empty until there is something true to say, and the space is held either
   way so filling it does not shove the page down. Nothing here is coloured
   as a verdict: a green or red band across the top of the front door would
   be a verdict about a whole country. */
/* GET ME THERE. Three apps, because guessing which one somebody navigates with
   is a guess this site does not need to make. Wrapping rather than scrolling:
   at 320px the three sit two-and-one, which is fine, and a horizontal scroller
   for three buttons is a worse answer. */
.dirs{margin:14px 0 0}
.dirlabel{margin:0 0 8px;font-size:14px;color:var(--dim)}
.dirlinks{display:flex;flex-wrap:wrap;gap:8px;margin:0}
.dirlinks .btn{text-decoration:none}
.dirnote{margin:10px 0 0;font-size:13px;color:var(--dim);max-width:62ch}
.livewarn{margin:0 0 10px;font-size:17px;line-height:1.45;min-height:1.45em;max-width:62ch}
.livewarn .n{font-variant-numeric:tabular-nums;font-weight:700}
/* ---------- the front door's suggestions --------------------------------- */
/* PLACE NAMES AND WHERE THEY ARE. NOTHING ABOUT TODAY'S WATER.
   No mark, no verdict word, no status colour — none of the .beach card's
   vocabulary, on purpose. A tick or a green row beside a name in a box the
   reader is still typing into would be a verdict given before the page that
   works one out has been opened, and it would go stale in the panel while
   they read it. The kind chip says what the thing IS, which is the same
   reasoning .fallrow already follows on /swimming/.

   IN FLOW, not floating. An absolutely positioned panel here would have to
   pass under a sticky masthead at z-index 1000, and at full width it would
   cover the one live sentence on this page — the warning count. Pushing the
   page down instead costs a layout shift while typing and hides nothing. */
.fsug{
  margin:10px 0 0;background:var(--card);
  border:1px solid var(--line);border-radius:var(--r-m);
  box-shadow:var(--lift-2);overflow:hidden;
}
/* 44px at EVERY width, not only under pointer:coarse — the .ordbtn rule's
   reasoning applies exactly: a row that navigates if you hit it, next to
   other rows that navigate somewhere else, is sized against a thumb. */
.fsugrow{
  display:flex;flex-direction:column;justify-content:center;
  min-height:44px;padding:8px 12px;
  text-decoration:none;color:inherit;
  border-bottom:1px solid var(--line);
}
.fsugrow:last-child{border-bottom:0}
.fsugrow .bname{font-size:16px}
.fsugrow:hover,.fsugrow[aria-selected=true]{background:var(--bg-sunk)}
.fsugrow[aria-selected=true]{box-shadow:inset 3px 0 0 var(--brand-bright)}
/* What the thing is, where a verdict would be. Deliberately the quietest text
   in the row: it is a category, not a recommendation. */
.fsugkind{
  display:inline-block;margin-left:8px;padding:1px 7px;border-radius:999px;
  font-size:11px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;
  background:var(--bg-sunk);color:var(--dim-strong);
  border:1px solid var(--line-strong);vertical-align:1px;
}
.fsugmore{margin:0;padding:8px 12px;font-size:13px;color:var(--dim);
  border-top:1px solid var(--line)}
.fsugnote{margin:0;padding:8px 12px;font-size:12.5px;color:var(--dim);
  background:var(--bg-sunk);border-top:1px solid var(--line)}
/* Near me is an <a> so that it still goes somewhere with no JavaScript, and
   .btn was only ever written for buttons. */
a.btn{text-decoration:none}
/* Windows high contrast throws away background-color and box-shadow, which is
   exactly what the highlighted row leans on. Ask for the system's own
   selected-item pair instead. LAST in this block on purpose, the way the
   .btn.save.on rule is: same specificity, so source order decides. */
@media (forced-colors:active){
  .fsugrow[aria-selected=true]{
    background:Highlight;color:HighlightText;forced-color-adjust:none}
}
.covers{display:flex;flex-wrap:wrap;gap:8px;margin:12px 0 14px;font-size:14px}
.covers a{
  /* 44px tall, and reached with padding rather than min-height: these are
     inline-block, so a min-height grows the box without moving the line box
     and the text sits high in a lopsided pill. Same trick .pill and .ffpill
     already use. */
  display:inline-block;padding:12px 15px;border-radius:999px;text-decoration:none;
  background:var(--card);border:1px solid var(--line-strong);color:var(--ink);
  font-weight:600;box-shadow:var(--lift);
}
.covers a:hover{border-color:var(--brand-bright);color:var(--brand-bright)}
.sub.quiet{font-size:14px;color:var(--dim);margin:0 0 14px}

.searchrow{display:flex;gap:10px}
.field{position:relative;flex:1}
.field input{
  width:100%;padding:9px 14px 9px 38px;font-size:16px;
  border:1px solid var(--line-strong);border-radius:999px;
  background:var(--card);color:var(--ink);box-shadow:var(--lift);
}
.field input:focus{border-color:var(--brand-bright)}
.field svg{position:absolute;left:13px;top:50%;transform:translateY(-50%);opacity:.5}

.summary{display:flex;gap:10px 8px;flex-wrap:wrap;margin:4px 0 16px}

/* Your beaches, and the button that puts them there. */
.mine{margin:18px 0 4px}
.mine h2{margin:0 0 10px}
.saverow{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin:16px 0 0}
.btn.save{background:var(--card);color:var(--ink);border:1px solid var(--line-strong);
  font-weight:650}
.btn.save.on{background:var(--brand);border-color:var(--brand);color:#fff}
/* Busy, not disabled — see the note in saved.js. It keeps focus and stays
   readable; it just stops looking pressable while the work is in flight. */
.btn[aria-disabled=true]{opacity:.72;cursor:default}
/* THE FILLED PILL WAS INVISIBLE IN DARK MODE. --brand is #0e3941 there and
   --card is #141d1f: 1.37:1 between them, against 10.45:1 in light mode. So on
   a phone at night — which is when people check the water for the morning — the
   strongest signal that a toggle had been pressed was simply absent, and every
   save button on all 1,586 place pages had the same fault. --brand-bright is
   the dark palette's own accent and takes it to 8.71:1, with the page ground as
   the ink on top at 9.60:1. */
@media (prefers-color-scheme:dark){
  .btn.save.on{background:var(--brand-bright);border-color:var(--brand-bright);
    color:var(--bg)}
}
/* Windows high contrast throws away background-color, which is exactly the
   signal the on state leans on. Ask for the system's own selected-item pair
   instead. LAST on purpose: it has the same specificity as the dark rule above,
   so placed before it, the dark rule would simply win and this would never do
   what its own comment claims. */
@media (forced-colors:active){
  .btn.save.on{background:Highlight;color:HighlightText;border-color:Highlight;
    forced-color-adjust:none}
}
.savenote{font-size:13px;color:var(--dim);flex:1 1 14em;min-width:0}

/* ---------- putting the saved list in your own order ---------------------- */
/* One bar per section: two arrays, two storage keys, two vocabularies. A beach
   cannot be moved above a waterfall in any sense that means anything. */
.ordbar{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin:0 0 12px}
.ordsaid{font-size:13px;color:var(--dim);flex:1 1 12em;min-width:0;line-height:1.45}
/* NOT :empty{display:none}. A live region taken out of the tree until text
   lands in it loses its first announcement on NVDA and JAWS, which is every
   announcement here — the reader hears nothing the first time they move
   something. It keeps its box and stays empty instead. */
/* THE WARNINGS BLOCK on the front door. Its own panel, above everything, and
   deliberately the loudest thing on the page: it took over from a menu item,
   so it has to be at least as findable as the menu item was. */
.warnbox{border:1px solid var(--line-strong);border-radius:var(--r-m);
  background:var(--card);box-shadow:var(--lift);padding:16px 18px;margin:0 0 20px}
.warnboxh{font:650 15px/1.3 var(--sans);margin:0 0 12px;
  text-transform:uppercase;letter-spacing:.06em;color:var(--dim)}
/* RED ONLY WHEN SOMETHING IS WARNED. The heading and the box edge take the
   warning colour when there is a count above zero, and stay neutral on a quiet
   day and on a day the readings could not be read — a red box shouting about
   an alarm it is not carrying is how a reader learns to ignore the red. */
.warnbox.has-warnings{border-color:var(--avoid-line);background:var(--avoid-bg)}
.warnbox.has-warnings .warnboxh{color:var(--avoid)}
.warnage{margin:12px 0 0}
/* The pills are the answer, so they lead. The line under them is the age and
   the overlap — necessary, and not the thing to read first. */
.warnpills{display:flex;flex-wrap:wrap;gap:10px;margin:0}
.warnpills[hidden]{display:none}
.warnbox .livewarn{margin:12px 0 0;font-size:13.5px;line-height:1.5;color:var(--dim)}
.warnbox .livewarn:empty{display:none}
/* On the red ground the pill needs its own surface or it disappears into it. */
.warnbox.has-warnings .freshness{background:var(--card)}
/* ...except when there are no pills, which is when the words ARE the answer. */
.warnbox .warnpills[hidden] + .livewarn{margin:0;font-size:16.5px;color:var(--ink)}
.warnpill{gap:8px}
.warnpill{font-size:15px}
.warnpill .n{font-variant-numeric:tabular-nums;font-weight:700;font-size:17px;
  color:var(--avoid)}
.warnpill[hidden]{display:none}
@media (max-width:420px){
  .warnpills{flex-direction:column;align-items:stretch}
  .warnpill{justify-content:center}
}
/* The scope controls on /warnings/ itself. */
#wscope{display:flex;gap:8px;flex-wrap:wrap;margin:0 0 10px}
#wscope .pill{text-decoration:none}

.ordsaid:empty{min-height:1px}
/* The saved page's waterfall half carries its own date, because the pill at
   the top of the page belongs to the beach readings. Quieter than a pill and
   in the flow, so it reads as a note about the list under it. */
.flowage{font-size:13px;color:var(--dim);margin:-4px 0 10px}
/* The saved page's page-level status line sits above the sections rather
   than inside a flex bar, so it needs its own margins — and none at all
   when it is empty, which is most of the time. */
#savedbody > .ordsaid{margin:0 0 10px}
#savedbody > .ordsaid:empty{margin:0}
/* --brand is #0e3941 in dark mode against a #0c1214 page: about 1.5:1, so a
   brand-filled pressed state is invisible at night. Same fault, same fix as
   .btn.save.on. */
.ordedit[aria-expanded=true]{background:var(--brand);border-color:var(--brand);color:#fff}
@media (prefers-color-scheme:dark){
  .ordedit[aria-expanded=true]{background:var(--brand-bright);
    border-color:var(--brand-bright);color:var(--bg)}
}

/* REAL BUTTONS, NOT TEXT LINKS. Sized against a thumb rather than against the
   Remove link beside them: an arrow you have to aim at, on the page holding
   your own list, next to a card that navigates if you miss it. 44px is the
   floor everywhere here, not only under pointer:coarse. */
.ordbtn{
  min-width:46px;min-height:44px;padding:0 10px;
  display:inline-flex;align-items:center;justify-content:center;
  font:inherit;font-size:19px;line-height:1;cursor:pointer;
  color:var(--brand-bright);background:var(--card);
  border:1px solid var(--line-strong);border-radius:10px;
}
.ordbtn:hover{border-color:var(--brand-bright)}
/* At the ends of the list. NOT [disabled]: a disabled button drops the focus
   standing on it and stops being announced, and pressing it still has something
   worth saying — that this one is already first. */
.ordbtn[aria-disabled=true]{color:var(--dim);border-color:var(--line);opacity:.65}
/* The row of actions under a saved card. Keeps its 16px away from the link
   above it: these sit directly beneath a card that navigates when tapped. */
.beach>.saverow{margin:14px 0 0;padding:0 16px 14px;gap:10px 12px;align-items:center}
.pushrow{display:flex;align-items:flex-start;gap:12px;flex-wrap:wrap;margin:14px 0 0;
  padding-top:14px;border-top:1px solid var(--line)}
.pushrow .savenote{line-height:1.5}
.pill{
  display:inline-flex;align-items:center;gap:8px;padding:8px 14px;border-radius:999px;
  font-size:14px;font-weight:650;background:var(--card);box-shadow:var(--lift);
  border:1px solid var(--line);
}
.pill .n{font-variant-numeric:tabular-nums;font-weight:700}
.pill.avoid{color:var(--avoid);background:var(--avoid-bg);border-color:var(--avoid-line)}
.pill.advised{color:var(--advised);background:var(--advised-bg);border-color:var(--advised-line)}
.pill.caution{color:var(--caution);background:var(--caution-bg);border-color:var(--caution-line)}
.pill.ok{color:var(--ok);background:var(--ok-bg);border-color:var(--ok-line)}
.pill.unknown{color:var(--unknown);background:var(--unknown-bg);border-color:var(--unknown-line)}
.pill[aria-pressed=true]{outline:2px solid currentColor;outline-offset:1px}
/* ...but a keyboard ring must win over the "pressed" outline, on this row as
   well as the area row below. Same specificity trap, same fix. */
.pill[aria-pressed=true]:focus-visible{outline:3px solid var(--focus);outline-offset:2px}
/* The area row sits under the status row and is quieter: status is what you came
   for, area is how you narrow it. */
.regions{margin:-6px 0 16px}
.regions .pill{background:var(--card);color:var(--ink-soft);border-color:var(--line);font-weight:550}
.regions .pill:hover{border-color:var(--brand-bright);color:var(--ink)}
.regions .pill[aria-pressed=true]{background:var(--brand);color:var(--brand-ink);
  border-color:var(--brand);outline:none}
/* ...but not while it has keyboard focus. The rule above outranks :focus-visible,
   and the list re-render deliberately returns focus to this button, so a keyboard
   user was left standing on a control with no ring at all. */
.regions .pill[aria-pressed=true]:focus-visible{outline:3px solid var(--focus);outline-offset:2px}
.regions .pill[aria-pressed=true] .n{color:#fff}
/* The lifeguard chip is a toggle, like the area pills and unlike the status
   pills, which carry their own verdict colour. A 2px outline is too quiet for
   something that takes the list from 941 to 232, so it gets the same solid
   treatment the area pills get when they are on. */
/* A FEATURED filter, deliberately not looking like the pills either side of it.
   The status pills are a verdict and the area pills are a place: both answer
   "which of these". This one answers "only the ones with a lifeguard", which is
   a different kind of question, and looking identical to its neighbours made it
   read as just another category. So it carries a lifebuoy, sits on its own row,
   and is drawn in the brand colour rather than in card grey — present and
   obviously switchable when off, filled and unmistakable when on. */
#features{margin-top:10px}
.pill.feature{gap:7px;border-color:var(--brand-bright);color:var(--feature-ink);
  background:var(--card);border-width:1.5px}
.pill.feature .fic{flex:none;opacity:.9}
.pill.feature .n{color:inherit}
/* currentColor, not --brand: on a dark ground --brand is darker than the
   border it replaces, so hovering made the chip harder to see instead of
   easier. */
.pill.feature:hover{border-color:currentColor;background:var(--bg-sunk)}
#features .pill[aria-pressed=true]{background:var(--brand);color:var(--brand-ink);
  border-color:var(--brand);outline:none;box-shadow:var(--lift-2)}
/* WINDOWS HIGH CONTRAST. forced-colors throws away background-color, which is
   the only thing distinguishing a pressed filter from an unpressed one — so in
   that mode the whole area and lifeguard row read as identical, with no way to
   see which filter is on. Border and outline survive, so the state is carried
   by those instead. */
@media (forced-colors: active){
  .regions .pill[aria-pressed=true],
  #features .pill[aria-pressed=true],
  /* Near me. It says it is on by swapping its fill and its text colour for the
     page's — both of which forced-colors throws away — so a located reader and
     an unlocated one saw the same button. The status pills below are already
     safe: theirs is an outline, and outlines survive. */
  /* The waterfall page's Area and Country pills say they are on by swapping
     their background, which forced-colors discards — so every one of them read
     as off. Left out of the original pass because they are .ffpill and not
     .pill. */
  /* The attribute twice, which costs nothing and buys specificity. A media
     query adds none, so these were tied with .ffpill[aria-pressed=true] at
     line 1147 and .btn.primary[aria-pressed=true] at 1468 — both of which come
     LATER in the sheet and therefore won. In Windows High Contrast a pressed
     Near me painted a pale --line-strong edge on a near-identical ground, so
     located and unlocated looked the same: exactly the failure this block was
     written to remove, undone by two rules further down. */
  .ffpill[aria-pressed=true][aria-pressed],
  .btn.primary[aria-pressed=true][aria-pressed]{
    border:3px solid Highlight;
    outline:1px solid CanvasText;
    forced-color-adjust:none;
    background:Canvas;
    color:CanvasText;
  }
}
#features .pill[aria-pressed=true] .n,
#features .pill[aria-pressed=true] .fic{color:#fff;opacity:1}
@media (forced-colors: active){
  /* forced-color-adjust:none is INHERITED, so the block above stops these two
     being colour-forced and they paint their literal #fff — white on a white
     Canvas in a light high-contrast theme, which is the count and the lifebuoy
     gone. They follow the button they sit in. */
  .regions .pill[aria-pressed=true] .n,
  #features .pill[aria-pressed=true] .n,
  #features .pill[aria-pressed=true] .fic{color:CanvasText}
}
#features .pill[aria-pressed=true]:focus-visible{outline:3px solid var(--focus);outline-offset:2px}

.banner{
  border:1px solid var(--caution-line);border-left:4px solid var(--caution);
  background:var(--caution-bg);color:var(--caution);
  border-radius:var(--r-s);padding:12px 15px;font-size:14.5px;margin:0 0 12px;
}
#banner .banner:first-child{margin-top:12px}

/* ---------- lists and cards ---------------------------------------------- */
/* Two columns once there is room. A single column across 1,120px gives each card
   a line of text and about nine hundred pixels of nothing, and makes a list of
   seventy-nine warnings much longer to scroll than it needs to be. One column
   below 900px, where two would squeeze the reasons. minmax(0,...) so a long
   beach name cannot push a column wider than its share. */
.list{display:grid;gap:10px}
@media (min-width:900px){
  .list{grid-template-columns:repeat(2, minmax(0, 1fr));gap:12px}
}
/* Cards in a row are the same height, because one carrying a "Not checked" line
   and its neighbour not carrying one left a ragged gap down the middle of the
   page. The grid stretches them; the two rules below make the LINK fill that
   height too, so a taller card has no dead strip along the bottom that looks
   clickable and is not. */
.beach{
  background:var(--card);border:1px solid var(--line);border-radius:var(--r-m);
  box-shadow:var(--lift);transition:border-color .15s, box-shadow .15s, transform .15s;
  display:flex;flex-direction:column;
}
.beach>h3.bh{flex:1;display:flex}
/* The padding on a card lives on `.beach a.head`, the inner button. Three
   pages build the card flat instead — /surfing/, /lifeguards/ and the water
   company pages emit <a class="beach"> with the contents directly inside and no
   .head at all — so that rule never matched them and their text ran to within a
   pixel of the border. Give the flat shape the same 16px. `a.beach` hits only
   those: every card built in JavaScript is an <article class="beach"> with the
   inner .head, so it is untouched. */
a.beach{text-decoration:none;color:inherit;padding:16px}
/* On the flat card the tick is a row of its own above the name, and sat on it. */
a.beach>.mark{margin-bottom:10px}
.beach:hover{border-color:var(--line-strong);box-shadow:var(--lift-2)}
h3.bh{margin:0;font:inherit}
.beach a.head,.beach button.head{
  width:100%;flex:1;display:flex;gap:14px;align-items:flex-start;text-align:left;
  padding:16px;background:none;border:0;color:inherit;border-radius:var(--r-m);text-decoration:none;
}
/* The status mark on a list card. Small on purpose: it repeats twenty-five times
   down the page and the words beside it are what people actually read, so it is
   a marker rather than a headline. The one on a beach page is the answer to the
   question and stays big — see .verdictcard .mark below. */
.mark{
  flex:none;width:26px;height:26px;border-radius:8px;display:grid;place-items:center;
  font-weight:700;font-size:13px;border:1px solid transparent;
}
.mark.ok{background:var(--ok-bg);color:var(--ok);border-color:var(--ok-line)}
.mark.caution{background:var(--caution-bg);color:var(--caution);border-color:var(--caution-line)}
.mark.avoid{background:var(--avoid-bg);color:var(--avoid);border-color:var(--avoid-line)}
.mark.advised{background:var(--advised-bg);color:var(--advised);border-color:var(--advised-line)}
.mark.unknown{background:var(--unknown-bg);color:var(--unknown);border-color:var(--unknown-line)}
.bmain{flex:1;min-width:0}
.bname{font-weight:650;font-size:17.5px;letter-spacing:-.012em;display:block}
.bwhere{color:var(--dim);font-size:13px;margin-top:2px}
.bverdict{font-size:14.5px;margin-top:6px;font-weight:650}
.bverdict.ok{color:var(--ok)} .bverdict.caution{color:var(--caution)}
.bverdict.avoid{color:var(--avoid)} .bverdict.advised{color:var(--advised)}
.bverdict.unknown{color:var(--unknown)}
.breason{color:var(--ink-soft);font-size:14.5px;margin-top:3px}
.bgap{color:var(--caution);font-size:13px;margin-top:4px}
.bdist{flex:none;color:var(--dim);font-size:13px;font-variant-numeric:tabular-nums;padding-top:4px}

/* ---------- the verdict, on a single place -------------------------------- */
/* The heading this section never had. The conditions block below it says "Sea,
   wind and tide today" and reads as one thing; this one, which is the reason the
   page exists, simply started with a card and left two sentences loose under it. */
#conditions,.sect{background:var(--card);border:1px solid var(--line);
  border-radius:var(--r-l);box-shadow:var(--lift-2);padding:20px 22px;margin:16px 0}
#conditions>h2,.sect>h2{margin-top:0}
.sect>.sub:first-of-type{margin-top:0}
/* A section that renders nothing must not leave an empty card behind. */
.sect:empty{display:none}
@media (max-width:520px){ #conditions,.sect{padding:14px 15px} }
.scopebox{margin:16px 0 0;padding:12px 14px;background:var(--bg-sunk);
  border:1px solid var(--line);border-radius:var(--r-m)}
.scopebox h3{margin:0 0 6px;font:650 12px/1.3 var(--sans);text-transform:uppercase;
  letter-spacing:.08em;color:var(--dim)}
.scopebox .verdictscope{margin:0;background:none;border:0;padding:0}
.scopebox .nextbest{margin:8px 0 0}

/* THE REGULATOR'S RECORD, inside the verdict card but plainly not the verdict.
   Two sections used to carry this — "About this bathing water" and "How it has
   been rated" — six thousand pixels down the page. They are the same subject as
   the answer at the top and belong with it; they are a different TIMEFRAME, and
   a reader who takes last season's grade for today's answer has been misled by
   the layout. So: a rule across the card, and headings in the same small
   uppercase the scope box uses, which reads as a change of subject rather than
   as more of the verdict. */
/* A HARD DIVIDER, not a hairline. Jay asked for the line between the record
   and the verdict above it to read as a real break, because the record is a
   different subject and a 1px hairline the same colour as every other rule on
   the card did not say so. Full bleed across the card — the card's own 20/22px
   padding is cancelled with negative margins and given back as padding — so it
   reaches both edges instead of floating inside them. */
/* THE CARD'S PADDING IS 20px 22px AT EVERY WIDTH — .verdictcard has no
   breakpoint — so the negative margin that pulls this rule out to the card's
   edges is -22px at every width too. It was -20px below 560px, leaving the
   "full bleed" divider 2px short of both edges on a phone, which is exactly
   where it is most visible.

   And --line-strong, not --line: in dark mode --line is #222e30 on a #141d1f
   card, about 1.2:1, so a 3px rule in it was three pixels of nothing. The whole
   point of the band is that the hairline did not read as a break. */
.record{margin:26px -22px 0;padding:24px 22px 0;
  border-top:3px solid var(--line-strong)}
@media (min-width:560px){
  .record{padding-top:26px}
}
/* The record's two halves: the prose, and the fact grid it used to sit above
   with a column of white space beside it. */
.recordsplit{display:grid;gap:18px 26px;align-items:start;
  grid-template-columns:minmax(0,1fr)}
@media (min-width:820px){
  .recordsplit{grid-template-columns:minmax(0,1.15fr) minmax(0,0.85fr)}
}
/* Two up rather than four across, now that they live in a column — but only
   where two fit. This was an unconditional two-track grid with a floor of 0, so
   on a 320px phone it squeezed "Monitored outfalls within 5km" into 145px, and
   it did the same in the 820–1000px band where the column itself is narrow. The
   shared .facts rule already knows the answer: 158px is the narrowest a fact
   card reads at, and auto-fit gives two when two fit and one when they do not. */
.recordfile .facts{grid-template-columns:repeat(auto-fit,minmax(158px,1fr))}
.record h3{margin:0 0 6px;font:650 12px/1.3 var(--sans);text-transform:uppercase;
  letter-spacing:.08em;color:var(--dim)}
.record h3 + .sub{margin-top:0}
.record .clshist{margin-top:10px}
.record .prose{margin-top:12px}
.record .prose p{font-size:14.5px}
/* The second heading needs air above it: the prose it follows is the end of a
   different question. */
.record .prose + h3{margin-top:20px}
.record .facts{margin-top:10px}
.verdictcard{
  background:var(--card);border:1px solid var(--line);border-radius:var(--r-l);
  box-shadow:var(--lift-2);padding:20px 22px;margin:16px 0;
}
/* VERDICT LEFT, FORTNIGHT RIGHT. The verdict is a mark and four words — 218px
   of a 1,080px card — and the fourteen-day strip was two thousand pixels down
   the page beside a map that did not need the width. minmax(0,…) on both tracks
   because a grid item's default min-width is its content, which is how a strip
   of fourteen cells pushes a card wider than its parent. */
/* The left column: the verdict, then the reasons under it. minmax(0,…) on the
   track keeps it from being widened by its own content; this keeps the column
   itself from being widened by a long reason line. */
.verdictmain{min-width:0}
.verdictmain #v-body{margin-top:18px}
.verdicttop{display:grid;gap:18px 26px;align-items:start;
  grid-template-columns:minmax(0,1fr)}
/* TWO COLUMNS ONLY WHEN THERE IS SOMETHING IN THE SECOND ONE. The strip is
   built hidden and revealed by script, so with no JavaScript — or before the
   script runs — the card declared a second track that nothing occupied, and
   the verdict sat in the left half of a card whose right half was blank. :has
   is unsupported in old browsers, where the card stays one column: narrower
   than intended, never half empty. */
@media (min-width:820px){
  .verdicttop:has(> #spillhist:not([hidden])){
    grid-template-columns:minmax(0,1fr) minmax(0,1fr)
  }
}
/* The strip is a second subject, not more of the verdict, so it carries the
   same small uppercase heading the scope box and the record band use. */
.stripside h3{margin:0 0 6px;font:650 12px/1.3 var(--sans);text-transform:uppercase;
  letter-spacing:.08em;color:var(--dim)}
.stripside .sub{margin:0 0 10px}
.stripside{min-width:0}
.verdictcard .big{display:flex;gap:16px;align-items:center}
.verdictcard .mark{width:58px;height:58px;font-size:26px;border-radius:16px}
.verdictcard .word{font:400 clamp(24px,3.4vw,32px)/1.05 var(--serif);letter-spacing:-.01em}
.verdictcard .word.ok{color:var(--ok)} .verdictcard .word.caution{color:var(--caution)}
.verdictcard .word.avoid{color:var(--avoid)} .verdictcard .word.advised{color:var(--advised)}
.verdictcard .word.unknown{color:var(--unknown)}
.verdictcard .when{color:var(--dim);font-size:13.5px;margin-top:5px}

.why{list-style:none;margin:20px 0 0;padding:0;display:flex;flex-direction:column;gap:12px}
.why li{display:flex;gap:12px;align-items:flex-start}
.why .ic{flex:none;width:24px;height:24px;border-radius:8px;display:grid;place-items:center;
  font-size:12px;margin-top:1px;font-weight:700}
.why .txt{flex:1}
.why .src{display:block;color:var(--dim);font-size:12.5px;margin-top:2px}
/* What the reason above it actually means. Quieter than the reason, because it
   is the explanation and not the fact, but not so quiet it reads as small
   print — this is the sentence that answers "yes, but why?". */
.why .means{color:var(--ink-soft);font-size:13.5px;line-height:1.5;margin-top:5px}
.subhead{font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;
  color:var(--dim);margin:22px 0 8px}
.checks{list-style:none;margin:0;padding:0;font-size:14.5px}
.checks li{display:flex;gap:10px;padding:4px 0}
.checks .t{color:var(--ok);font-weight:700;flex:none}
.checks .g{color:var(--caution);font-weight:700;flex:none}

.facts{display:grid;grid-template-columns:repeat(auto-fit,minmax(158px,1fr));gap:10px;margin-top:16px}
.fact{background:var(--bg-sunk);border:1px solid var(--line);border-radius:var(--r-s);padding:11px 13px}
.fact dd .factnote{display:block;font-size:12px;font-weight:400;color:var(--dim);margin-top:2px}
.fact dt{color:var(--dim);font-size:12.5px;margin:0 0 3px}
.fact dd{margin:0;font-weight:650;font-size:15.5px}

details.more{margin-top:16px;font-size:14.5px}
details.more summary{cursor:pointer;color:var(--brand-bright);font-weight:600}

/* The two things you can do with this page, side by side as pills rather than
   stacked as links. One was static markup and one is built by the script, which
   is why they looked like unrelated leftovers at the bottom of the section. */
/* Said where it happened. This used to go into the banner stack at the top of
   the page, next to notices about data feeds. */
.nearnote{margin:10px 0 0;padding:9px 12px;border-radius:var(--r-s);
  background:var(--caution-bg);border:1px solid var(--caution-line);
  color:var(--ink);font-size:13px;line-height:1.45}
.nearnote:empty{display:none}

.pillrow{display:flex;flex-wrap:wrap;gap:10px;align-items:flex-start;margin-top:12px}
.pillrow > *{flex:1 1 240px;margin-top:0}
.pillrow details.more>summary{width:100%;justify-content:center}
.pillrow details.more>summary,.pillrow #reports details.more>summary{
  display:inline-flex;align-items:center;gap:8px;padding:10px 16px;min-height:44px;
  border-radius:999px;font-size:14px;font-weight:650;background:var(--card);
  box-shadow:var(--lift);border:1px solid var(--line);color:var(--ink-soft);
  list-style:none;box-sizing:border-box}
.pillrow summary::-webkit-details-marker{display:none}
.pillrow details.more>summary:hover{border-color:var(--brand-bright);color:var(--ink)}
.pillrow details.more>summary::before{content:"+";color:var(--dim);font-weight:700}
.pillrow details.more[open]>summary::before{content:"\2212"}
/* Opened, it needs the whole row: a form or an embed box does not belong in a
   column 220px wide beside a pill. */
.pillrow details.more[open]{flex-basis:100%}
.pillrow #reports{flex:1 1 240px}
.pillrow #reports>details.more{width:100%}

table{width:100%;border-collapse:collapse;margin-top:10px;font-size:13.5px}
th,td{text-align:left;padding:8px 10px;border-bottom:1px solid var(--line)}
th{color:var(--dim);font-weight:600;font-size:12.5px;text-transform:uppercase;letter-spacing:.05em}
td.st{white-space:nowrap;font-weight:650}
@media (max-width:560px){
  /* Three columns and a nowrap status ran ~50px past a 320px phone, and because
     nothing contained it the whole document scrolled sideways. Letting the
     status wrap takes back the width; containing it in the disclosure means any
     that is left scrolls the table rather than the page. */
  td.st{white-space:normal}
  table{font-size:13px}
  th,td{padding:7px 5px}
  td:first-child{overflow-wrap:anywhere}
  /* The heading row was the widest thing in the table, not the data: RECEIVING
     WATERCOURSE set in uppercase with .05em of letter-spacing wanted 296px on a
     320px phone, so the three columns still ran past the edge and the Status
     column — the only one anybody opens this for — sat outside the frame,
     reachable by a sideways drag inside a <details> that shows no scrollbar on
     iOS. Sentence case, no tracking, and the whole thing fits.
     details.more keeps its overflow-x below as the safety net: a longer
     watercourse name or a wider system font scrolls the table, never the page. */
  th{text-transform:none;letter-spacing:0;font-size:12px;white-space:normal}
  /* "1.4km" never wraps, and sits against the name it belongs to. */
  th:nth-child(2),td:nth-child(2){white-space:nowrap;text-align:right}
  /* No right gutter on the last column: the table is already flush to the
     disclosure's edge, and those 5px are what the status words need. */
  th:nth-child(3),td.st{padding-right:0}
  details.more{overflow-x:auto;-webkit-overflow-scrolling:touch}
}
td.st.now{color:var(--avoid)} td.st.recent{color:var(--caution)}
td.st.off{color:var(--caution)} td.st.clear{color:var(--ok)}

/* ---------- media -------------------------------------------------------- */
figure.photo{margin:16px 0 6px}
figure.photo img{width:100%;height:auto;max-height:440px;object-fit:cover;
  border-radius:var(--r-m);border:1px solid var(--line);display:block;background:var(--bg-sunk)}
figure.photo figcaption{color:var(--dim);font-size:12.5px;margin-top:8px}

.beachmap{height:360px;border-radius:var(--r-m);border:1px solid var(--line);margin:12px 0 8px;
  background:var(--bg-sunk);display:grid;place-items:center;color:var(--dim);font-size:14px;
  position:relative;z-index:0;isolation:isolate}
.beachmap.failed{display:none}
.maphint{display:none;color:var(--dim);font-size:13px;margin:0 0 8px}
#map,#wmap,#smap{height:clamp(460px, 78vh, 820px);border-radius:var(--r-m);border:1px solid var(--line);
  margin-bottom:10px;background:var(--bg-sunk);position:relative;z-index:0;isolation:isolate}
.bigmap{height:clamp(520px, 82vh, 900px);border-radius:var(--r-m);border:1px solid var(--line);
  margin:14px 0 8px;background:var(--bg-sunk);display:grid;place-items:center;color:var(--dim);
  position:relative;z-index:0;isolation:isolate}
/* A map that could not load should not hold 82vh of empty grey. Both big maps
   set this class on failure and neither had a rule for it, so /map/ and
   /waterfalls/ kept a full-height panel with one line of apology in the middle
   of it. Shrunk to the words, which is all there is to show. */
.bigmap.failed{height:auto;min-height:0;padding:18px 16px;text-align:center;
  line-height:1.5}
@media (orientation:landscape) and (max-height:560px){
  /* A phone on its side is 375px tall. The clamp floors above (460 and 520)
     are taller than that, so the map was bigger than the screen holding it. */
  #map,#wmap,#smap{height:74vh}
  .bigmap{height:74vh}
  .beachmap{height:64vh}
}
.legend{gap:14px;flex-wrap:wrap;font-size:12.5px;color:var(--dim);margin:0 0 16px;display:none}
.leaflet-container{border-radius:calc(var(--r-m) - 1px);font:inherit}
/* Leaflet styles its own popup and knows nothing about the site's themes, so in
   dark mode it stayed a white card while the text inside it turned light —
   the tide and swell line came out at 1.6:1. Theme the card, not the text. */
@media (prefers-color-scheme:dark){
  /* Every selector here is prefixed with .leaflet-container on purpose. Leaflet's
     stylesheet is fetched at runtime, so it lands after this one and wins any tie
     on source order — the same reason the zoom buttons above name their control
     class. Its rules are 0,1,0; these are 0,2,0 and win without !important. */
  .leaflet-container .leaflet-popup-content-wrapper,
  .leaflet-container .leaflet-popup-tip{
    background:var(--card);color:var(--ink);box-shadow:0 3px 14px rgba(0,0,0,.5)}
  .leaflet-container .leaflet-popup-content a{color:var(--brand-bright)}
  .leaflet-container a.leaflet-popup-close-button{color:var(--dim)}
}
/* A map is worth most of a desktop screen and rather less of a phone's, where it
   also swallows the scroll: Leaflet takes the touch, so a finger landing inside
   pans the map instead of moving the page. Half the screen leaves room to get
   past it. */
@media (max-width:599px){
  #map,#wmap,#smap{height:clamp(300px, 52vh, 520px)}
  .bigmap{height:clamp(340px, 58vh, 560px)}
  /* The outfall map on every beach and waterfall page. Leaflet claims the touch
     here too, so it gets the same treatment as the other two rather than being
     the one map left filling half a phone with no way past it. */
  .beachmap{height:clamp(220px, 34vh, 320px)}
}

/* ---------- tiles and prose ---------------------------------------------- */
.prose{max-width:66ch}
.prose p{margin:12px 0}
.prose ul{margin:12px 0;padding-left:22px}
.prose li{margin:6px 0}
/* 224px minimum put one waterfall per row on a phone, which for a list of 676
   is a great deal of scrolling to see very little. 150px fits two on the
   narrowest screen and four or five on a laptop. */
.tiles{display:flex;flex-wrap:wrap;gap:10px;margin-top:14px;justify-content:center}
.tiles .tile{flex:1 1 300px}
[hidden]:not(.navcount){display:none!important}
/* The flow, on the tile. A waterfall in spate is the one where people are hurt,
   because it is at its most impressive on exactly the day it is most dangerous,
   so it has to be visible in the list rather than three clicks in. The mark sits
   in the corner so the tile stays two lines and a phone still fits plenty. */
.tile{position:relative}
.fmark{position:absolute;top:9px;right:9px;width:19px;height:19px;border-radius:6px;
  display:flex;align-items:center;justify-content:center;font-size:11px;
  font-weight:700;line-height:1}
.fmark.ok{background:var(--ok-bg);color:var(--ok);border:1px solid var(--ok-line)}
.fmark.caution{background:var(--caution-bg);color:var(--caution);border:1px solid var(--caution-line)}
.fmark.avoid{background:var(--avoid-bg);color:var(--avoid);border:1px solid var(--avoid-line)}
.fmark.unknown{background:var(--bg-sunk);color:var(--dim);border:1px solid var(--line)}
.tile>b{padding-right:24px;display:block}
.fflow{display:block;font-size:11.5px;margin-top:5px;font-weight:650;color:var(--dim)}
.fflow.avoid{color:var(--avoid)} .fflow.caution{color:var(--caution)}
.fflow.ok{color:var(--ok)} .fflow.unknown{color:var(--dim);font-weight:400}
.fdist{display:block;font-size:11.5px;margin-top:3px;color:var(--brand-bright);
  font-weight:650;font-variant-numeric:tabular-nums}
.fwx{display:block;font-size:11px;margin-top:8px;color:var(--ink);
  background:var(--caution-bg);border:1px solid var(--caution-line);
  border-radius:var(--r-s);padding:2px 6px}
.tile{background:var(--card);border:1px solid var(--line);border-radius:var(--r-m);
  padding:11px 12px;text-decoration:none;color:inherit;box-shadow:var(--lift);display:block;
  transition:border-color .15s, box-shadow .15s}
@media (min-width:700px){.tile{padding:14px 15px}}
.tile:hover{border-color:var(--brand-bright);box-shadow:var(--lift-2)}

/* The waterfall filter.
   674 waterfalls listed under five country headings and nothing else was not a
   list anyone could use: seven of them are called High Force, and the only way
   to reach one in Kerry was to scroll past everything in England and Scotland.
   Each waterfall now carries its county, which both tells the seven apart and
   gives the list something to filter on.

   Progressive enhancement: the panel is hidden in the markup and shown by
   script, so without JavaScript the full list is still there, just unfiltered. */
/* ONE FILTER PANEL, on all three list pages.
   The swimming and surfing pages had a bordered search row in the static HTML
   and the waterfalls page built its own bordered card in JavaScript, with its
   own class family, its own input styling and no magnifier. Same job, two
   appearances, because they were written months apart. Now one. */
.filters{margin:14px 0 4px;padding:12px;background:var(--card);
  border:1px solid var(--line);border-radius:var(--r-m);box-shadow:var(--lift)}
.filters .search{position:static;padding:0;background:none;z-index:auto}
.filters .summary{margin:10px 0 0}
.filters .areawrap{margin-top:10px}
.filters .ffbar{margin-top:10px}
.ffpills{display:flex;flex-wrap:wrap;gap:10px 6px;margin-top:10px}
.ffpill{font:inherit;font-size:13px;padding:5px 11px;border-radius:999px;cursor:pointer;
  border:1px solid var(--line);background:var(--bg);color:var(--ink)}
.ffpill[aria-pressed=true]{background:var(--brand);border-color:var(--brand);color:#fff}
.ffpill:focus-visible{outline:2px solid var(--brand-bright);outline-offset:2px}
.ffareas{margin-top:10px}
.ffareas>summary{cursor:pointer;font-size:13.5px;font-weight:600;padding:4px 0;
  list-style:none}
.ffareas>summary::-webkit-details-marker{display:none}
.ffareas>summary::before{content:"▸ ";color:var(--dim)}
.ffareas[open]>summary::before{content:"▾ "}
/* auto-fit, not a hard 1fr 1fr. Two fixed columns meant a 320px phone gave each
   area name about 125px, so "Argyll and Bute 14" and "Northern Ireland 23" broke
   over three lines each and the 238px window held four areas. The track never
   goes below 160px now, which is one column on a phone and the same two and
   three columns as before everywhere else. */
.ffalist{max-height:238px;overflow-y:auto;margin-top:8px;padding:2px;
  display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:2px 10px}
.ffalist label{display:flex;align-items:center;gap:7px;font-size:13px;padding:4px 2px;
  cursor:pointer;line-height:1.25}
.ffalist input{margin:0;flex:none}
.ffalist .n{color:var(--dim);font-size:11.5px;margin-left:auto;padding-left:6px}
.ffbar{display:flex;align-items:center;gap:12px;margin-top:10px;flex-wrap:wrap}
.ffcount{font-size:13px;color:var(--dim);margin:0}
.ffclear{font:inherit;font-size:13px;background:none;border:0;padding:0;cursor:pointer;
  color:var(--brand-bright);text-decoration:underline}
.ffclear[hidden]{display:none}
.ffnote{font-size:12px;color:var(--dim);margin:7px 0 0}
/* The lifeguard caveat, which sits inside the chip row and has to break
   onto its own line rather than sitting beside the pills. */
.lgnote{flex-basis:100%;margin:8px 0 0;font-size:12.5px;line-height:1.5;color:var(--dim)}
.lgnote a{color:inherit}
.ffnone{margin:18px 0;padding:14px;border:1px dashed var(--line);border-radius:var(--r-m);
  color:var(--dim);font-size:14px}
.tile .area{display:block;font-size:11.5px;color:var(--dim);margin-top:2px}

/* The week ahead, and how cold the water is.
   Seven columns that scroll sideways on a narrow phone rather than being
   squeezed to nothing. */
.week{display:grid;grid-auto-flow:column;
  grid-auto-columns:minmax(64px,1fr);gap:6px;
  margin:12px 0 0;overflow-x:auto;padding-bottom:4px;
  /* Seven days need 484px and a small phone has 248. Half the week was off the
     right-hand edge with nothing to say it was there. The mask fades the last
     column as a hint, and snapping stops a flick landing mid-column. */
  scroll-snap-type:x proximity;
  -webkit-overflow-scrolling:touch}
.week>*{scroll-snap-align:start}
/* Four of the seven days are off the right-hand edge on a small phone and
   nothing said so. A gradient would have faded a real day's forecast, so the
   strip says it plainly instead.
   MEASURED, not assumed. This used to fire on width alone, so a strip showing
   the last three days of the week — which fits — still told the reader to swipe
   for days that were not there. drawWeek() sets .overflows when the strip
   really is wider than its frame, and clears it when it is not. */
#weekwrap.overflows::after{content:"Swipe for the rest of the week \2192";
  display:block;margin-top:7px;font-size:12.5px;color:var(--dim)}
/* A BUTTON NOW, so it needs the button reset: browsers give one their own font,
   a grey ground and a centred inline layout, none of which this wants. */
.wday{background:var(--card);border:1px solid var(--line);border-radius:var(--r-s);
  padding:9px 4px;text-align:center;min-width:0;
  font:inherit;color:inherit;cursor:pointer;display:block;width:100%;
  transition:border-color .12s, box-shadow .12s}
.wday:hover{border-color:var(--brand-bright)}
.wday[aria-expanded=true]{border-color:var(--brand);
  box-shadow:inset 0 0 0 2px var(--brand)}
/* The open day and today can both be true at once; the open one wins, because
   it is the thing the reader just did. */
.wday.today[aria-expanded=true]{box-shadow:inset 0 0 0 2px var(--brand)}

/* The panel a tapped day opens. A sibling of the strip, not a child of a
   column: a day column is about two characters wide on a 320px phone. */
.wdaydetail{margin:10px 0 0;padding:12px 14px;border:1px solid var(--line);
  border-radius:var(--r-s);background:var(--bg-sunk)}
.wdaytitle{margin:0 0 8px;font:650 15px/1.3 var(--sans)}
.wdayfacts{margin:0}
.wdaytide{margin:10px 0 0;font-size:14px}
.wdaytide.dim{color:var(--dim)}
.wdaynone{margin:0;font-size:14px;color:var(--dim)}
@media (forced-colors: active){
  /* The open day is marked by an inset shadow, which forced-colors discards. */
  .wday[aria-expanded=true]{border:3px solid Highlight; forced-color-adjust:none;
    background:Canvas; color:CanvasText}
}
.wday.today{border-color:var(--brand-bright);box-shadow:inset 0 0 0 1px var(--brand-bright)}
.wday .wsym{color:var(--dim)}
.wday.k-sun .wsym,.wday.k-part .wsym{color:var(--wsym-sun)}
.wday.k-rain .wsym{color:var(--wsym-rain)}
.wday.k-snow .wsym{color:var(--wsym-snow)}
.wday.k-storm .wsym{color:#c2410c}
.wday.k-sun{background:linear-gradient(180deg,#ffeec2,#fffaf0 70%);border-color:#f0d089}
.wday.k-part{background:linear-gradient(180deg,#fff0d4,#fffbf3 70%);border-color:#f2d9a8}
.wday.k-rain{background:linear-gradient(180deg,#d4e9f7,#f2f9fd 70%);border-color:#a8cfe8}
.wday.k-snow{background:linear-gradient(180deg,#dcecf8,#f4fafd 70%);border-color:#b3d6ea}
.wday.k-storm{background:linear-gradient(180deg,#fbdcc8,#fef6f1 70%);border-color:#f0b795}
.wday .whi{color:var(--ink);font-weight:650}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .wday.k-sun{background:linear-gradient(180deg,#54401a,var(--card) 70%);border-color:#7a5d24}
  :root:not([data-theme="light"]) .wday.k-part{background:linear-gradient(180deg,#4a3f22,var(--card) 70%);border-color:#6d5c33}
  :root:not([data-theme="light"]) .wday.k-rain{background:linear-gradient(180deg,#1b3d52,var(--card) 70%);border-color:#2a5d7d}
  :root:not([data-theme="light"]) .wday.k-snow{background:linear-gradient(180deg,#204152,var(--card) 70%);border-color:#316179}
  :root:not([data-theme="light"]) .wday.k-storm{background:linear-gradient(180deg,#4d2716,var(--card) 70%);border-color:#7a3c22}
  :root:not([data-theme="light"]) .wday.k-sun .wsym,
  :root:not([data-theme="light"]) .wday.k-part .wsym{color:#e8b45c}
  :root:not([data-theme="light"]) .wday.k-rain .wsym{color:#7fb6d6}
  :root:not([data-theme="light"]) .wday.k-snow .wsym{color:#9cc4dc}
  :root:not([data-theme="light"]) .wday.k-storm .wsym{color:#e08a69}
}
:root[data-theme="dark"] .wday.k-sun{background:linear-gradient(180deg,#54401a,var(--card) 70%);border-color:#7a5d24}
:root[data-theme="dark"] .wday.k-part{background:linear-gradient(180deg,#4a3f22,var(--card) 70%);border-color:#6d5c33}
:root[data-theme="dark"] .wday.k-rain{background:linear-gradient(180deg,#1b3d52,var(--card) 70%);border-color:#2a5d7d}
:root[data-theme="dark"] .wday.k-snow{background:linear-gradient(180deg,#204152,var(--card) 70%);border-color:#316179}
:root[data-theme="dark"] .wday.k-storm{background:linear-gradient(180deg,#4d2716,var(--card) 70%);border-color:#7a3c22}
:root[data-theme="dark"] .wday.k-sun .wsym,
:root[data-theme="dark"] .wday.k-part .wsym{color:#e8b45c}
:root[data-theme="dark"] .wday.k-rain .wsym{color:#7fb6d6}
:root[data-theme="dark"] .wday.k-snow .wsym{color:#9cc4dc}
:root[data-theme="dark"] .wday.k-storm .wsym{color:#e08a69}
.wday b{display:block;font-size:12px;font-weight:650;letter-spacing:.01em}
.wday .wsym{display:block;font-size:21px;line-height:1.25;margin:2px 0}
.wday .whi{display:block;font-size:14px;font-weight:600}
.wday .wlo{display:block;font-size:12px;color:var(--dim)}
.wday .wrain{display:block;font-size:11px;color:var(--dim);margin-top:3px}
.wday .wrain.wet{color:var(--advised);font-weight:600}
/* ---------- the conditions panel ----------------------------------------- */
/* Built from the SAME pill as the verdict card above it, .facts and .fact, so
   the two blocks read as one page. The old version was a flex container of
   full-width spans, which rendered as a flat list of bold-lead sentences that
   you had to read to navigate. */
.condgrid{margin-top:10px;
  grid-template-columns:repeat(auto-fit,minmax(146px,1fr))}
.fact.cond dd{font-size:19px;letter-spacing:-.01em}
.fact.cond .condsub{margin-top:3px;color:var(--dim);font-size:12px;line-height:1.35}

/* Warnings are not readings and must not look like one. Full width, under the
   pills, carrying the weight they had before this was rebuilt. */
.condwarn{margin-top:8px;padding:9px 11px;border-radius:var(--r-s);
  background:var(--caution-bg);border:1px solid var(--caution-line);
  color:var(--ink);font-size:12.5px;line-height:1.45}
.condwarn b{font-weight:700}
/* The offshore wind box stays the loudest thing in the section. It is the only
   warning here describing something a swimmer cannot see and would otherwise
   read as a good day, and it is why the beach-facing data was built at all. */
.condwarn.offshore{background:var(--advised-bg);border-color:var(--advised-line);
  font-size:13px;line-height:1.5;margin-top:10px}
.condwarn.offshore b{display:block;margin-bottom:2px}

/* Three days of tide in three lines. A row per day, turns inline, so twelve
   times cost about ninety pixels rather than twelve rows. */
.tidedays{margin-top:14px}
.tidedays .subhead{margin:0 0 6px}
.tideday{display:flex;gap:10px;align-items:baseline;padding:5px 0;
  border-top:1px solid var(--line)}
.tideday > b{flex:0 0 46px;font-weight:650;font-size:13px}
.tidetimes{display:flex;flex-wrap:wrap;gap:4px 12px;font-size:13px;
  font-variant-numeric:tabular-nums}
.tt i{font-style:normal;color:var(--dim-strong);font-size:11.5px;
  text-transform:uppercase;letter-spacing:.04em;margin-right:2px}
.tt.past{color:var(--dim)}
.tt.past i{opacity:.7}
.tidenote2{margin:8px 0 0;font-size:11.5px;line-height:1.4;color:var(--dim)}

/* Space kept for the live readings, so nothing below them moves when they
   arrive. This was the whole of the mobile CLS score: 0.313 on a beach page,
   because #conditions and #weekwrap are empty in the HTML and #verdict grows
   from 293px to over 600.

   Every number here is the SMALLEST filled height measured across a coastal
   beach, an estuary, an inland lake and one with no marine grid point nearby
   (Ainsdale, Crackington Haven, Blyth South, Coniston), at 375px and at
   1100px. Smallest, so the reservation can never leave a gap under a loaded
   page — it can only be too small, never too big. Re-measure if the panels
   gain a row. */
#verdict{min-height:460px}
/* RESERVED ON THE CUE, NOT ON THE SECTION. #conditions ships with the `hidden`
   attribute, and a display:none element occupies nothing however large its
   min-height — so this rule reserved exactly zero pixels and the shift it was
   written to prevent happened anyway. #weekcue is the empty div that sits in
   the flow where the section will appear, it is not hidden, and it has always
   been the element with a position. It holds the space instead, and gives it up
   the moment the section fills.

   260 + 246 measured separately on a coastal beach, an estuary, an inland lake
   and one with no marine grid point; the two h3s the merge added only ever make
   the real box taller, which is the safe direction for a floor. */
#weekcue{min-height:506px}
/* Released only when the section actually FILLED. weekTrouble() also unhides
   #conditions — to say it could not — and that box is one sentence tall, so
   zeroing the reservation there dropped everything below it by about 360px at
   the exact moment the reservation exists to cover. .filled is set by the
   success path alone. */
#conditions.filled ~ #weekcue,
#conditions.settled ~ #weekcue{min-height:0}
#conditions{min-height:0}
#weekwrap{min-height:246px}
/* Same 16px floor as .embedbox: under it, iOS Safari zooms in on focus and
   never zooms back out. */
#reportnote,.filters input,.filters textarea{font-size:16px}
@media (max-width:560px){
  #verdict{min-height:611px}
  #weekcue{min-height:583px}
  #weekwrap{min-height:254px}
}
/* The caveats that will not fit in a 162px pill. Quiet, because they qualify a
   reading rather than warning about one, but never absent: both of these went
   missing when the readings became pills and the weekly facts check is what
   noticed. */
.condnote{margin:8px 0 0;font-size:11.5px;line-height:1.45;color:var(--dim)}

@media (max-width:560px){
  .condgrid .fact:last-child:nth-child(odd){grid-column:1 / -1}
}
@media (max-width:420px){
  .fact.cond dd{font-size:17.5px}
  .tideday > b{flex-basis:40px;font-size:12.5px}
  .tidetimes{gap:3px 9px;font-size:12.5px}
}

.bsurf{display:block;font-size:13px;color:var(--ink-soft);margin-top:3px;line-height:1.45}
.bsurf.none{color:var(--dim);font-style:normal}
.verdictscope{font-size:13px;color:var(--ink-soft);margin:10px 0 0;padding:9px 12px;
  background:var(--bg-sunk);border-radius:var(--r-s);line-height:1.45}
.prose.safety{background:var(--bg-sunk);border-radius:var(--r-m);padding:14px 16px;
  border:1px solid var(--line);margin:14px 0}
.prose.safety p{margin:0 0 10px}
.prose.safety p:last-child{margin-bottom:0;font-size:13.5px;color:var(--dim)}
.leaflet-popup-content .ptide{color:var(--ink-soft);font-size:12.5px}

/* Said only when somebody tries to drag the map with one finger. See
   touchGuard() — one finger scrolls the page, two move the map. */
.mapgrab{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  z-index:1000;pointer-events:none;opacity:0;transition:opacity .18s ease;
  background:rgba(12,18,20,.86);color:#fff;font:600 14px/1.35 var(--sans);
  padding:11px 15px;border-radius:10px;text-align:center;max-width:86%;
  box-shadow:0 4px 18px rgba(0,0,0,.3)}
.mapgrab.on{opacity:1}
@media (prefers-reduced-motion:reduce){.mapgrab{transition:none}}
.weeknote{font-size:12.5px;color:var(--dim);margin:10px 0 0}

/* The classification year by year, oldest on the left. Colour follows the four
   classes rather than the daily verdict palette, because a classification is a
   different thing from today's answer and must not be mistaken for it. */
.clshist{display:flex;gap:6px;margin:12px 0 0;flex-wrap:wrap}
.cy{flex:1 1 84px;min-width:78px;padding:9px 8px;border-radius:var(--r-s);
  text-align:center;border:1px solid var(--line);background:var(--bg-sunk)}
.cy b{display:block;font-size:12px;color:var(--dim-strong);font-weight:600}
.cy span{display:block;font-size:13.5px;font-weight:650;margin-top:2px}
.cy-excellent{background:var(--ok-bg);border-color:var(--ok-line)}
.cy-good{background:var(--ok-bg);border-color:var(--ok-line);opacity:.82}
.cy-sufficient{background:var(--caution-bg);border-color:var(--caution-line)}
.cy-poor{background:var(--avoid-bg);border-color:var(--avoid-line)}

/* Beach awards, on the card that already carries the verdict.
   NAMED, NEVER MERGED. Scotland's Beach Award is not a Blue Flag and Wales's
   Green Coast Award is not either; they are different tests set by different
   bodies, and a single generic "award-winning" mark would say something none of
   them says. Blue Flag gets the blue it is named for; the rest are quieter,
   because they are the second tier on this page and the page says so. */
.awards{display:flex;flex-wrap:wrap;gap:5px;margin-top:7px}
.award{font-size:11.5px;font-weight:650;line-height:1.5;padding:1px 8px;
  border-radius:999px;border:1px solid var(--line);color:var(--dim);
  background:var(--card);white-space:nowrap}
.award.a-blue-flag{color:#0b4a86;background:#e2eff8;border-color:#a1cdea}
@media (prefers-color-scheme:dark){
  .award.a-blue-flag{color:#9ad3f7;background:#153345;border-color:#205b81}
}
@media (forced-colors: active){
  /* The blue is the whole of the distinction, and forced-colors discards it. */
  .award{border:1px solid CanvasText}
  .award.a-blue-flag{border:3px solid Highlight; forced-color-adjust:none;
    background:Canvas; color:CanvasText}
}

/* The awards a beach holds, on its own page.
   ITS OWN BLOCK, never inside the verdict card. An award is annual and the
   verdict is half-hourly, and two beaches on this site hold an award and are
   under advice against bathing today. In one box a reader would average them. */
/* The freshness pill and the award marks, on one line under the heading. */
.toppills{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:10px 0 0}
.toppills .freshness{margin-top:0}
/* A link, so it takes you to the block that explains it — and it has to look
   like the same mark as on the best-and-worst cards, because it is.
   On this row it takes the freshness pill's own metrics: 13px on 5px/11px
   padding. Two pills side by side at different heights read as an accident. */
a.award{text-decoration:none;display:inline-flex;align-items:center;gap:6px}
a.award:hover{border-color:var(--brand-bright)}
a.award .awardmark{color:inherit}
.toppills a.award{font-size:13px;padding:5px 11px;box-shadow:var(--lift)}
@media (pointer:coarse){ .toppills a.award{min-height:32px} }

/* THE SAME CARD AS EVERY OTHER SECTION. This carried --bg-sunk, the tone used
   for insets like the scope box and the rain figures, plus a tighter radius and
   no lift — so on a page of white cards the awards sat in a grey one and read
   as a panel dropped in from somewhere else. It is a section like the rest, and
   .sect already says what a section looks like; these three lines were only
   overriding it. Jay: "I want it the same colour scheme as the rest." */
.awardbox{padding:20px 22px}
@media (max-width:520px){ .awardbox{padding:14px 15px} }
.awardbox h2{margin:0 0 10px;font-size:17px}
.awardlist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:9px}
.awardrow{display:grid;grid-template-columns:auto auto 1fr;align-items:baseline;
  gap:8px;font-size:14.5px}
@media (max-width:520px){
  /* The description goes under the name rather than beside it: at 320px a third
     column leaves about nine characters for it. */
  .awardrow{grid-template-columns:auto 1fr}
  .awardrow .awardwhat{grid-column:1 / -1;padding-left:23px}
}
.awardmark{flex:none;align-self:center;color:var(--dim)}
.awardrow.a-blue-flag .awardmark{color:#0b4a86}
@media (prefers-color-scheme:dark){
  .awardrow.a-blue-flag .awardmark{color:#57a0d5}
}
.awardname{font-weight:650}
.awardwhat{color:var(--dim);font-size:13.5px;line-height:1.45}
.awardbox .stripnote{margin-top:12px}

/* The embed offer on a beach page. The badge is a fixed 300px wide, which is
   wider than the content column on a 320px phone, so the preview pushed the
   whole page sideways. Capped here rather than in the badge itself: the width
   attribute is what somebody pastes onto their own site, and it should stay
   300. */
.badgeprev{margin:10px 0}
.badgeprev iframe{max-width:100%}
/* 16px, not 12.5: iOS Safari zooms the whole page in when a field smaller than
   that is focused, and offers no way back out. */
.embedbox{width:100%;box-sizing:border-box;font:16px/1.5 ui-monospace,SFMono-Regular,
  Menlo,Consolas,monospace;padding:10px 11px;border:1px solid var(--line);
  border-radius:var(--r-s);background:var(--bg-sunk);color:var(--ink);resize:vertical}
.embedbox:focus-visible{outline:2px solid var(--brand-bright);outline-offset:1px}
.nextbest{margin:12px 0 0;padding:12px 14px;border-radius:var(--r-s);
  background:var(--ok-bg);border:1px solid var(--ok-line);font-size:14.5px}
.nextbest a{font-weight:650}

/* The fourteen area filters are 356px of the homepage, which on a phone pushed
   the first actual result to 1060px down an 812px screen: you had to scroll past
   the whole of the furniture to see a single beach. Folded away on narrow
   screens, left open where there is room for it. Marked open in the markup and
   closed by script, so with no JavaScript nothing is hidden. */
.areawrap{margin:0}
.areawrap>summary{cursor:pointer;font-size:13.5px;font-weight:600;padding:7px 0;
  color:var(--ink-soft);list-style:none;display:inline-flex;align-items:center;gap:6px}
.areawrap>summary::-webkit-details-marker{display:none}
.areawrap>summary::before{content:"▸";color:var(--dim);font-size:11px}
.areawrap[open]>summary::before{content:"▾"}
@media (min-width:700px){ .areawrap>summary{display:none} }
.btn.primary{background:var(--brand);border-color:var(--brand);color:#fff;
  font-weight:650}
.btn.primary svg{flex:none}
.btn.primary[aria-pressed=true]{background:var(--bg);color:var(--ink);
  border-color:var(--line-strong)}


/* Fourteen days of discharges, one cell a day.
   FOUR states, not two. A day the collector did not run is drawn hollow and a
   day every nearby monitor was offline is drawn dashed grey: neither is a day
   without sewage, and colouring either green would be inventing good news. */
.strip{display:flex;gap:3px;margin:10px 0 6px;max-width:520px}
.strip .d{flex:1 1 0;min-width:0;height:34px;border-radius:4px;position:relative;
  background:var(--ok-bg);border:1px solid var(--ok-line)}
.strip .d.spill{background:var(--avoid-bg);border-color:var(--avoid-line)}
.strip .d.none{background:transparent;border:1px dashed var(--line)}
/* A FOURTH STATE. Every monitor near the beach was offline all day, so nothing
   could have been recorded — which is not the same fact as a day nobody
   collected (that is .none) and certainly not the same as a clear day. Dashed
   like .none because both mean "no reading", filled like nothing else because
   the reason differs. */
.strip .d.dark{background:var(--unknown-bg);border:1px dashed var(--unknown-line)}
.strip .d span{position:absolute;left:0;right:0;bottom:-16px;text-align:center;
  font-size:9.5px;color:var(--dim);line-height:1}
.stripnote{font-size:13px;color:var(--dim);margin:22px 0 0}
.striplegend{display:flex;gap:14px;flex-wrap:wrap;font-size:12px;color:var(--dim);
  margin:20px 0 0;align-items:center}
.striplegend i{display:inline-block;width:12px;height:12px;border-radius:3px;
  margin-right:5px;vertical-align:-2px}
.striplegend .k{display:inline-flex;align-items:center}
/* The key's own swatches. The strip's colours are scoped to .strip .d, so the
   three states have to be restated here — same tokens, so they cannot drift. */
.striplegend i.ok{background:var(--ok-bg);border:1px solid var(--ok-line)}
.striplegend i.spill{background:var(--avoid-bg);border:1px solid var(--avoid-line)}
.striplegend i.none{background:transparent;border:1px dashed var(--line)}
.striplegend i.dark{background:var(--unknown-bg);border:1px dashed var(--unknown-line)}
/* Same again for the fortnight strip. The four states are told apart only by
   fill, which forced-colors discards — leaving fourteen identical squares.
   Borders carry it instead: heavy for a discharge, thin for checked, dashed for
   not checked, dotted for monitors offline. */
@media (forced-colors: active){
  .strip .d{border:1px solid CanvasText; forced-color-adjust:none; background:Canvas}
  .strip .d.spill{border:4px solid CanvasText}
  .strip .d.none{border:1px dashed GrayText}
  /* Dotted, so it is neither the thin solid of a checked day nor the dashed of
     an uncollected one. Three distinguishable borders, four states with the
     discharge's heavy one. */
  .strip .d.dark{border:2px dotted CanvasText}
  .striplegend i{forced-color-adjust:none; background:Canvas; border:1px solid CanvasText}
  .striplegend i.spill{border:4px solid CanvasText}
  .striplegend i.none{border:1px dashed GrayText}
  .striplegend i.dark{border:2px dotted CanvasText}
}
.tile b{display:block;font-size:14.5px;font-weight:650;letter-spacing:-.01em;line-height:1.25}
@media (min-width:700px){.tile b{font-size:15.5px}}
.tile span{color:var(--dim);font-size:13px}
.empty{text-align:center;color:var(--dim);padding:48px 10px;line-height:1.7}
/* A button that has to read as a link, because it belongs mid-sentence. */
.linkish{background:none;border:0;padding:0;font:inherit;color:var(--brand-bright);
  text-decoration:underline;cursor:pointer}
/* --brand is #0e3941 in dark mode on a #141d1f page: 1.51:1, so hovering the
   link made it vanish. Same fault and same fix as the feature chips. */
.linkish:hover{color:var(--feature-ink)}
.more-btn{margin:18px auto 0;display:block}

/* The map beside what discharges near it, rather than a full width band with
   nothing next to it. Below 900px it stacks, text first and map second, which is
   the source order, so nothing has to be reordered for a phone. */
.mapsplit{display:grid;grid-template-columns:1fr;gap:14px;margin-top:10px}
.mapsplit .beachmap{margin-top:0}
.mapsplit #spillhist{margin-top:14px}
.mapsplit h3{font:650 15px/1.3 var(--sans);margin:0 0 4px}
#outfalllist{margin-top:14px}
#outfalllist table{font-size:13px}
#outfalllist details.more{margin:0}
@media (min-width:900px){
  /* TWO COLUMNS ONLY WHEN THE SECOND ONE EARNS ITS PLACE. On a beach whose
     nearest monitored overflow is outside the 2km this site counts — Dymchurch,
     whose only one is 3.0km away — there is no list of pipes to put beside the
     map, so the left column held a two-line caption and three buttons against a
     380px map and the rest was white space. Jay saw it straight away.

     The script adds .haslist when it actually fills the list. Without it the
     section is one column: caption and directions above a full-width map, which
     is the same furniture with none of the hole. */
  .mapsplit.haslist{grid-template-columns:minmax(0,0.9fr) minmax(0,1.1fr);gap:22px;
    align-items:start}
  .mapsplit{gap:16px}
  /* The outfall list sits under the directions and fills the column beside the
     map. Its own scroll if a beach has a lot of them, so a page with fourteen
     pipes does not stretch the section past the map it belongs to. */
  #outfalllist{margin-top:14px;max-height:230px;overflow:auto}
  /* The map no longer stretches to a row height set by the other column: that
     is what left five hundred pixels of white space in the middle of this
     section once the fortnight strip moved into the verdict card. It is a fixed
     comfortable height, and each column is as tall as its own content. */
  .mapsplit .beachmap{height:380px;min-height:320px}
}

/* ---------- footer ------------------------------------------------------- */
.sitefoot{background:var(--brand);border-top:1px solid var(--brand);margin-top:56px;
  padding:48px 0 32px;color:#a9c6c9;font-size:14px}
.footgrid{max-width:var(--wrap);margin:0 auto;padding:0 20px;
  display:grid;grid-template-columns:1.5fr 1fr 1fr;gap:34px}
@media (max-width:720px){
  .footgrid{grid-template-columns:1fr 1fr;gap:22px 18px}
  .footgrid > :first-child{grid-column:1 / -1}
  .footnote{display:none}
  .sitefoot{padding:30px 0 22px}
  .sitefoot li{margin:2px 0}
  .footbar{margin-top:20px}
  .stat{gap:10px;font-size:12.5px}
}
.sitefoot h2{font:650 12px/1.3 var(--sans);text-transform:uppercase;letter-spacing:.08em;
  color:#e8f4f4;margin:0 0 10px}
.sitefoot ul{list-style:none;margin:0;padding:0}
.sitefoot li{margin:6px 0}
.sitefoot a{color:#d6e7e8;text-decoration:none}
.sitefoot a:hover{color:#fff;text-decoration:underline}
.footbrand{font:400 22px/1.2 var(--serif);color:#fff;margin:0 0 8px;
  display:inline-flex;align-items:center;gap:9px}
.footnote{max-width:44ch;margin:0 0 12px}
.footbar{max-width:var(--wrap);margin:28px auto 0;padding:20px 20px 0;
  border-top:1px solid rgba(255,255,255,.16);font-size:12.5px;line-height:1.6}
.footbar strong{color:#fff}
.footbar a{text-decoration:underline;text-underline-offset:2px}
.stat{display:flex;gap:18px;flex-wrap:wrap;margin:10px 0 0;font-size:13px}
.stat b{color:#fff;font-weight:650;font-variant-numeric:tabular-nums}
