/* The colour tokens that differ between the themes, registered as lists of
   numbers so the browser can interpolate them when the theme changes (see
   .kl-theme-changing below). The initial values are the dark ones, which :root
   sets anyway; a browser without @property treats these as ordinary custom
   properties and the change simply snaps. */
@property --ink-rgb { syntax: "<number>#"; inherits: true; initial-value: 255, 255, 255; }
@property --shade-rgb { syntax: "<number>#"; inherits: true; initial-value: 0, 0, 0; }
@property --pill-rgb { syntax: "<number>#"; inherits: true; initial-value: 235, 235, 235; }
@property --page-rgb { syntax: "<number>#"; inherits: true; initial-value: 41, 45, 50; }
@property --page-deep-rgb { syntax: "<number>#"; inherits: true; initial-value: 31, 35, 40; }
@property --page-raised-rgb { syntax: "<number>#"; inherits: true; initial-value: 50, 55, 62; }
@property --status-ok-rgb { syntax: "<number>#"; inherits: true; initial-value: 126, 217, 87; }
@property --status-early-rgb { syntax: "<number>#"; inherits: true; initial-value: 255, 209, 102; }
@property --status-late-rgb { syntax: "<number>#"; inherits: true; initial-value: 255, 143, 143; }
@property --live-rgb { syntax: "<number>#"; inherits: true; initial-value: 224, 70, 58; }
@property --accent-amber-rgb { syntax: "<number>#"; inherits: true; initial-value: 251, 191, 36; }
@property --accent-green-rgb { syntax: "<number>#"; inherits: true; initial-value: 134, 239, 172; }
@property --link-blue-rgb { syntax: "<number>#"; inherits: true; initial-value: 142, 202, 255; }
@property --nw-blue-rgb { syntax: "<number>#"; inherits: true; initial-value: 107, 174, 214; }
@property --chrome-top-rgb { syntax: "<number>#"; inherits: true; initial-value: 30, 35, 41; }
@property --chrome-bottom-rgb { syntax: "<number>#"; inherits: true; initial-value: 10, 14, 20; }
@property --chrome-hover-top-rgb { syntax: "<number>#"; inherits: true; initial-value: 34, 39, 46; }
@property --chrome-hover-bottom-rgb { syntax: "<number>#"; inherits: true; initial-value: 12, 16, 22; }
@property --chrome-deep-rgb { syntax: "<number>#"; inherits: true; initial-value: 8, 11, 16; }
@property --announce-deep-rgb { syntax: "<number>#"; inherits: true; initial-value: 20, 24, 30; }
@property --weather-deep-rgb { syntax: "<number>#"; inherits: true; initial-value: 20, 24, 28; }
@property --bus-overlay-rgb { syntax: "<number>#"; inherits: true; initial-value: 18, 21, 25; }
@property --bus-alert-text-rgb { syntax: "<number>#"; inherits: true; initial-value: 255, 179, 179; }
@property --rs-available-rgb { syntax: "<number>#"; inherits: true; initial-value: 140, 220, 160; }

:root,
:root[data-theme="light"] .contact-block {
  /* Colour tokens (KILN-76). Every colour on the site is one of these. Each is a
     bare triple so a rule can add its own alpha: rgba(var(--ink-rgb), 0.08).
     A light theme redefines them; the dark values here are the site as it
     has always looked. */
  /* Foreground and surfaces */
  --ink-rgb: 255, 255, 255;
  --shade-rgb: 0, 0, 0;
  --page-rgb: 41, 45, 50;
  --page-deep-rgb: 31, 35, 40;
  --page-raised-rgb: 50, 55, 62;
  --pill-rgb: 235, 235, 235;
  /* Status (conventions: travel, and amber means act before it is too late) */
  --status-ok-rgb: 126, 217, 87;
  --status-early-rgb: 255, 209, 102;
  --status-late-rgb: 255, 143, 143;
  --live-rgb: 224, 70, 58;
  --accent-amber-rgb: 251, 191, 36;
  --accent-green-rgb: 134, 239, 172;
  --form-ok-rgb: 42, 122, 75;
  --form-error-rgb: 192, 57, 43;
  --link-blue-rgb: 142, 202, 255;
  /* Met Office warnings (NSWWS style guide v1.4) and pollen */
  --nswws-yellow-rgb: 255, 233, 35;
  --nswws-amber-rgb: 255, 153, 0;
  --nswws-red-rgb: 202, 10, 55;
  --nswws-green-rgb: 196, 233, 12;
  --nswws-yellow-ink-rgb: 20, 17, 10;
  --nswws-amber-ink-rgb: 20, 15, 5;
  /* The guide sets white on red in either theme. Ink was white only while the
     page was dark, and in light put dark type on the red (KILN-76). */
  --nswws-red-ink-rgb: 255, 255, 255;
  --pollen-very-high-rgb: 204, 0, 51;
  --nw-blue-rgb: 107, 174, 214;
  /* Header, footer and announcement chrome */
  --chrome-top-rgb: 30, 35, 41;
  --chrome-bottom-rgb: 10, 14, 20;
  --chrome-hover-top-rgb: 34, 39, 46;
  --chrome-hover-bottom-rgb: 12, 16, 22;
  --chrome-deep-rgb: 8, 11, 16;
  --announce-deep-rgb: 20, 24, 30;
  --weather-deep-rgb: 20, 24, 28;
  /* Page colours */
  --bus-sign-ink-rgb: 16, 16, 16;
  --bus-sign-top-rgb: 255, 225, 77;
  --bus-sign-bottom-rgb: 245, 205, 18;
  --bus-overlay-rgb: 18, 21, 25;
  --bus-alert-text-rgb: 255, 179, 179;
  --bins-rose-rgb: 255, 208, 208;
  --bins-amber-rgb: 255, 224, 138;
  --bins-green-rgb: 155, 211, 74;
  --bins-red-rgb: 255, 119, 119;
  --parish-blue-rgb: 147, 197, 253;
  --parish-gold-rgb: 252, 211, 77;
  --parish-teal-rgb: 110, 231, 183;
  --parish-lilac-rgb: 216, 180, 254;
  --rs-available-rgb: 140, 220, 160;
  --spotify-rgb: 29, 185, 84;

  --page-bg: rgb(var(--page-rgb));
  --text: rgb(var(--ink-rgb));
  --muted: rgba(var(--ink-rgb), 0.68);
  --faint: rgba(var(--ink-rgb), 0.42);
  --line: rgba(var(--ink-rgb), 0.76);
  --dark-text: rgb(var(--page-rgb));
  --soft-panel: rgba(var(--ink-rgb), 0.08);
  --serif: MinionPro-Regular, Garamond, Georgia, serif;
  --script: "Alex Brush", "Snell Roundhand", "Brush Script MT", cursive;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}


/* ── Light theme (KILN-76) ───────────────────────────────────────────────────
   Set by theme.js on the website's own pages: from the device's setting, or
   from the Auto / Light / Dark toggle in the footer. The other sites that load
   this file don't run theme.js, so they stay dark until each is converted.

   It is the same tokens with light values. Ink is what text and the glass
   panels are drawn in, so it becomes the dark page's own blue-grey (#292d32)
   and the translucent white panels become translucent blue-grey tints: the
   dark page becomes the light page's text, logo and pills, which ties the two
   together. The page is a near-neutral pale grey, a shade warmer than cool: a
   cooler one with more blue in it read as cold (owner, 28 Sep 2026). The
   contact band is white in both themes: it takes the dark values back (in
   :root above). The status colours keep their meaning in darker shades that
   read on a light page; the Met Office colours are set by their style guide
   and stay. */
:root[data-theme="light"] {
  color-scheme: light;
  --ink-rgb: 41, 45, 50;
  --shade-rgb: 58, 62, 70;
  --page-rgb: 244, 244, 242;
  --page-deep-rgb: 233, 233, 230;
  --page-raised-rgb: 255, 255, 255;
  --pill-rgb: 41, 45, 50;
  --status-ok-rgb: 38, 128, 56;
  --status-early-rgb: 214, 160, 0;
  --status-late-rgb: 190, 38, 38;
  --live-rgb: 204, 50, 38;
  --accent-amber-rgb: 196, 140, 0;
  --accent-green-rgb: 28, 132, 76;
  --link-blue-rgb: 24, 96, 184;
  --nw-blue-rgb: 30, 108, 168;
  --chrome-top-rgb: 255, 255, 255;
  --chrome-bottom-rgb: 235, 235, 232;
  --chrome-hover-top-rgb: 255, 255, 255;
  --chrome-hover-bottom-rgb: 228, 228, 225;
  --chrome-deep-rgb: 233, 233, 230;
  --announce-deep-rgb: 250, 250, 249;
  --weather-deep-rgb: 250, 250, 249;
  --bus-overlay-rgb: 250, 250, 249;
  --bus-alert-text-rgb: 168, 30, 30;
  --rs-available-rgb: 30, 130, 70;
}

/* The dark page is lit from the top with a white glow; charcoal there would
   read as a smudge, so the light page gets a white one instead. */
:root[data-theme="light"] body {
  background:
    radial-gradient(circle at 50% 0%, rgba(var(--page-raised-rgb), 0.9), transparent 22rem),
    linear-gradient(180deg, rgb(var(--page-rgb)), rgb(var(--page-deep-rgb)));
}

/* Text shadows were there to lift white type off a dark page; on a light one
   they only blur it. */
:root[data-theme="light"] * {
  text-shadow: none !important;
}

/* A change of theme with the page open morphs every colour rather than
   snapping (owner, 28 Sep 2026). theme.js puts .kl-theme-changing on the root
   for the length of it; the tokens that differ between the themes are
   registered below, so they can be interpolated, and transition on the root
   alone, and everything drawn in them follows. The first attempt faded
   snapshots of the page (a view transition): the nav menu's blur fell out of
   the snapshot, so whatever lay behind it showed through and then snapped
   back. Here nothing is snapshotted and the blur never goes.
   While it runs, nothing else transitions: a link easing its own colour over
   0.15s would trail behind the rest. */
:root.kl-theme-changing {
  transition:
    --ink-rgb 0.4s ease, --shade-rgb 0.4s ease, --pill-rgb 0.4s ease,
    --page-rgb 0.4s ease, --page-deep-rgb 0.4s ease, --page-raised-rgb 0.4s ease,
    --status-ok-rgb 0.4s ease, --status-early-rgb 0.4s ease, --status-late-rgb 0.4s ease,
    --live-rgb 0.4s ease, --accent-amber-rgb 0.4s ease, --accent-green-rgb 0.4s ease,
    --link-blue-rgb 0.4s ease, --nw-blue-rgb 0.4s ease,
    --chrome-top-rgb 0.4s ease, --chrome-bottom-rgb 0.4s ease,
    --chrome-hover-top-rgb 0.4s ease, --chrome-hover-bottom-rgb 0.4s ease,
    --chrome-deep-rgb 0.4s ease, --announce-deep-rgb 0.4s ease,
    --weather-deep-rgb 0.4s ease, --bus-overlay-rgb 0.4s ease,
    --bus-alert-text-rgb 0.4s ease, --rs-available-rgb 0.4s ease,
    --ctrl-chip-text-rgb 0.4s ease, --ctrl-menu-rgb 0.4s ease,
    --ctrl-panel-rgb 0.4s ease, --ctrl-sheet-rgb 0.4s ease,
    --connect-danger-rgb 0.4s ease, --connect-danger-soft-rgb 0.4s ease,
    --connect-dialog-rgb 0.4s ease;
}

:root.kl-theme-changing * {
  transition: none !important;
}

/* The logo is a white PNG. In light it is drawn through its own shape in the
   ink colour instead, so it is exactly the blue-grey of the text. The URL is
   absolute because games, connect and the API's pages load this file too,
   and a mask is fetched with CORS, which /assets/images allows (_headers).
   The control apps point it at their own copy. During a change it is drawn
   that way in both themes, so it morphs with the ink rather than swapping
   from the picture to the shape at one end; theme.js has the mask loaded
   before it starts, and --kl-logo-mask is where it finds it. */
/* 3kl-rentals: each house has its own logo, so the Worker sets
   --kl-logo-mask on <html> from the hostname (src/site.js). This is only the
   fallback for a page served without it. */
:root {
  --kl-logo-mask: url("/assets/images/logo-94.svg");
}

:root[data-theme="light"] .site-logo-link,
:root.kl-theme-changing .site-logo-link {
  background: rgb(var(--ink-rgb));
  -webkit-mask: var(--kl-logo-mask) center / contain no-repeat;
  mask: var(--kl-logo-mask) center / contain no-repeat;
}

:root[data-theme="light"] .site-logo,
:root.kl-theme-changing .site-logo {
  opacity: 0;
}

* { box-sizing: border-box; }

/* `hidden` means hidden. The browser's own rule for it is a plain `display:
   none` that any author `display` on the same element beats — which is how
   the businesses page's Open now filter put the attribute on nineteen cards
   and not one of them went, the card being `display: flex`. Said once here,
   with the weight to win, so a script that sets `hidden` can trust it on
   every page rather than each stylesheet having to remember. */
[hidden] { display: none !important; }

html {
  min-height: 100%;
  background: var(--page-bg);
  /* Safari on a phone inflates type inside any block it decides is text-heavy,
     by a ratio it works out from the layout width — so turning the phone on
     its side blew the sentence and the list behind an opened roadworks row up
     to half again the size of the row above them, while the row itself, being
     a flex line, was left exactly as it was. Every size on this site is set in
     rem off a root nothing moves. This says so, and the type then reads at the
     size it was written at whichever way up the phone is. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  /* The contact block bleeds to both edges with `calc(50% - 50vw)`, and a
     viewport width counts the scrollbar while the room the page actually has
     does not. On a desktop with a scrollbar that is half a scrollbar of
     overhang each side, and a page that scrolls sideways by seven pixels for
     no reason anybody can see. Clipped rather than hidden: `clip` does not
     make a scroll container of the root, so nothing inside changes how it
     positions itself. `hidden` first for anything too old for `clip`, which
     that browser keeps because it drops the line it cannot read. */
  overflow-x: hidden;
  overflow-x: clip;
}

body {
  min-height: 100vh;
  margin: 0;
  color: var(--text);
  background:
    radial-gradient(circle at 50% 0%, rgba(var(--ink-rgb), 0.08), transparent 22rem),
    linear-gradient(180deg, rgba(var(--page-rgb), 1), rgba(var(--page-deep-rgb), 1));
  font-family: var(--serif);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* The page fades up, and the page is what is under the shell — not the body.
   The ticker and the LAN control footer are the body's own children and stay
   out of it: they carry over from the page before, and a chrome that fades
   out with one page and back in with the next is a chrome that snaps off and
   returns, which is exactly what it is not doing. The background is the
   body's too, so it is painted at once rather than faded up; it is the same
   on every page, so there is nothing to fade. */
.site-shell {
  opacity: 0;
  transition: opacity 1.5s ease;
}

body.loaded .site-shell {
  opacity: 1;
}

/* Arriving from another of the site's pages (page-load.js puts .kl-within on
   the root), the logo and the menu button were already there, in the same
   place, so they stay put rather than vanishing and fading back with the rest
   (owner, 29 Sep 2026). Opacity on the shell can't spare one thing inside it,
   so the shell stays opaque and everything beside the header bar fades
   instead: each sibling along the way down to it, whatever the page's own
   markup. The fade is a filter rather than opacity so that anything with an
   opacity of its own (a card that waits for its data, say) keeps it, the two
   multiplying. It is dropped once settled, as the shell's is. */
html.kl-within .site-shell {
  opacity: 1;
  transition: none;
}

html.kl-within :is(.site-shell, .site-shell :has(.header-bar)) > :not(.header-bar):not(:has(.header-bar)) {
  filter: opacity(0);
  transition: filter 1.5s ease;
}

html.kl-within body.loaded :is(.site-shell, .site-shell :has(.header-bar)) > :not(.header-bar):not(:has(.header-bar)) {
  filter: opacity(1);
}

html.kl-within body.settled :is(.site-shell, .site-shell :has(.header-bar)) > :not(.header-bar):not(:has(.header-bar)) {
  filter: none;
  transition: none;
}

/* ── Section switcher ───────────────────────────────────────────────────────
   The control page's tab bar, under a page's intro, wherever a page has two
   things to say rather than one: news and sport against weather and pollen,
   the parish minutes against the planning applications. It runs at every width
   rather than under a breakpoint — stacked sections make a long page on a
   desktop as well as a phone — and the spacing above comes from .page-intro's
   own padding just below, so it sits where every page's first block does.
   Driven by tabs.js, which finds the bar by `data-tabs`. */

.page-tabs {
  position: relative;
  display: flex;
  gap: 0;
  /* Capped and centred under the intro rather than stretched across the full
     shell, where two or three tabs would be a very wide row of words */
  width: 100%;
  max-width: 460px;
  margin: 0 auto 32px;
  padding: 3px;
  border: 1px solid rgba(var(--ink-rgb), 0.12);
  border-radius: 999px;
  background: rgba(var(--ink-rgb), 0.05);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}

/* Equal shares of the bar, so the pill can be sized and positioned by two
   numbers alone — no measuring, and nothing to recalculate when the viewport
   changes. `--tab-n` is set by tabs.js from the number of tabs it found, so a
   bar that grows a third tab is right without anything here changing; the
   translation is of the pill's own width, which is one tab either way. */
.page-tabs-indicator {
  position: absolute;
  top: 3px;
  bottom: 3px;
  left: 3px;
  width: calc((100% - 6px) / var(--tab-n, 2));
  border-radius: 999px;
  background: rgba(var(--ink-rgb), 0.14);
  box-shadow:
    0 1px 0 rgba(var(--ink-rgb), 0.08),
    0 4px 12px rgba(var(--shade-rgb), 0.2);
  transform: translateX(calc(var(--tab-i, 0) * 100%));
  transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
}

.page-tab {
  position: relative;
  z-index: 1;
  flex: 1;
  min-width: 0;
  padding: 0.5rem 0.3rem;
  border: 0;
  border-radius: 999px;
  background: none;
  font-family: var(--sans);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: rgba(var(--ink-rgb), 0.5);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color 0.2s ease;
}

.page-tab.is-active { color: rgb(var(--ink-rgb)); }

.page-tab:focus-visible {
  outline: 2px solid rgba(var(--ink-rgb), 0.75);
  outline-offset: -2px;
}

/* The travel page's ring, on a tab whose panel is still on its way. In the
   tab's own corner rather than in padding: the tabs are equal halves of the
   bar, so there is no padding to widen, and a ring at the edge of a half that
   wide is nowhere near the word in the middle of it. */
.page-tab.is-waiting::after {
  content: "";
  position: absolute;
  top: 50%;
  right: 0.6rem;
  width: 0.62rem;
  height: 0.62rem;
  margin-top: -0.31rem;
  border: 1.5px solid rgba(var(--ink-rgb), 0.25);
  border-top-color: rgb(var(--ink-rgb));
  border-radius: 50%;
  animation: page-tab-wait 700ms linear infinite;
}

@keyframes page-tab-wait {
  to { transform: rotate(360deg); }
}

/* One panel at a time */
.page-tab-panel { display: none; }
.page-tab-panel.is-active { display: block; }

.page-tab-panel.is-active.from-right { animation: page-tab-in-right 0.28s cubic-bezier(0.4, 0, 0.2, 1); }
.page-tab-panel.is-active.from-left  { animation: page-tab-in-left  0.28s cubic-bezier(0.4, 0, 0.2, 1); }

@keyframes page-tab-in-right {
  from { opacity: 0; transform: translateX(22px); }
  to   { opacity: 1; transform: none; }
}
@keyframes page-tab-in-left {
  from { opacity: 0; transform: translateX(-22px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .page-tabs-indicator { transition: none; }
  .page-tab-panel.is-active.from-right,
  .page-tab-panel.is-active.from-left { animation: none; }
  /* A still ring rather than a spinning one: it still says something is being
     waited on, without the part that was turned down */
  .page-tab.is-waiting::after { animation: none; border-color: rgba(var(--ink-rgb), 0.5); }
}

/* ── How things arrive ──────────────────────────────────────────────────────
   Nothing on this site simply appears. Everything a script writes onto a page
   comes in, and comes in according to what it is: a card rises into place and
   staggers behind the card before it; a row does the same, less and quicker;
   a block — a whole section, a hero — fades up as one; a line of text — a
   timestamp, a note, an error — only fades, since a line that moves is a line
   being watched; a heading fades a beat ahead of what it heads; a modal scales
   up behind an overlay dimming. Marked in markup with data-arrive and swept by
   KLArrive in page-load.js, which puts the class on, staggers siblings of a
   kind, and takes the class off again when the animation is done — so an
   animation never sits in the cascade beating a hover rule. */
.arrive-block   { animation: arrive-block 0.5s ease-in-out backwards; }
.arrive-card    { animation: arrive-card 0.4s cubic-bezier(0.2, 0.7, 0.3, 1) backwards; }
.arrive-row     { animation: arrive-row 0.28s cubic-bezier(0.2, 0.7, 0.3, 1) backwards; }
.arrive-line    { animation: arrive-line 0.35s ease backwards; }
.arrive-heading { animation: arrive-line 0.3s ease backwards; }
.arrive-modal   { animation: arrive-modal 0.26s cubic-bezier(0.2, 0.7, 0.3, 1) backwards; }
.arrive-overlay { animation: arrive-line 0.26s ease backwards; }

@keyframes arrive-block { from { opacity: 0; transform: translateY(6px); }  to { opacity: 1; transform: none; } }
@keyframes arrive-card  { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes arrive-row   { from { opacity: 0; transform: translateY(4px); }  to { opacity: 1; transform: none; } }
@keyframes arrive-line  { from { opacity: 0; } to { opacity: 1; } }
@keyframes arrive-modal { from { opacity: 0; transform: scale(0.98); } to { opacity: 1; transform: none; } }

/* Motion turned down: everything still arrives, and nothing travels to do it */
@media (prefers-reduced-motion: reduce) {
  .arrive-block, .arrive-card, .arrive-row, .arrive-modal { animation-name: arrive-line; }
}

/* The fade is a one-off, but the property that draws it is not: an opacity
   transition on the shell keeps the whole page a single composited layer for
   as long as it is declared, and every backdrop-filter on the page samples
   what is behind it against that layer. A repaint anywhere then has the
   whole page to re-composite, which is how one card updating can flicker the
   header. Dropped altogether once the fade has run — see page-load.js. */
body.settled .site-shell {
  opacity: 1;
  transition: none;
}

a {
  color: inherit;
}

.site-shell {
  width: min(1100px, calc(100% - 32px));
  margin: 0 auto;
}

/* =====================
   HOME PAGE HERO
   ===================== */

.site-hero {
  min-height: 85vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  padding: 0 0 220px;
  text-align: center;
}

.site-hero.compact {
  min-height: auto;
}

/* =====================
   INNER PAGE HEADER
   (logo + nav only)
   ===================== */

.site-header {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-bottom: 32px;
  text-align: center;
}


/* =====================
   INNER PAGE INTRO
   (title + optional description)
   ===================== */

.page-intro {
  padding: 0 0 32px;  /* was 16px */
  text-align: center;
}

.page-kicker {
  margin: 0 0 0.75rem;
  color: rgb(var(--ink-rgb));
  font-family: var(--serif);
  font-size: clamp(1.5rem, 4vw, 1.8rem);
  line-height: 1;
  text-align: center;
}

/* The line under a page title, on every page that has one */
.page-intro-text {
  margin: 0;
  color: rgba(var(--ink-rgb), 0.72);
  font-family: var(--sans);
  font-size: clamp(0.9rem, 2.4vw, 1.05rem);
  text-align: center;
}

/* =====================
   SHARED NAV + LOGO
   ===================== */

.site-logo-link {
  display: inline-flex;
  justify-content: center;
  width: min(209px, 52vw);
}

.site-logo {
  display: block;
  width: 100%;
  height: auto;
}

/* =====================
   HEADER BAR + HAMBURGER
   ===================== */

.header-bar {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 100%;
  padding: 42px 0 0;
  gap: 12px;
}

.nav-toggle {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 1px solid rgba(var(--ink-rgb), 0.18);
  border-radius: 0.75rem;
  background: rgba(var(--ink-rgb), 0.075);
  color: rgba(var(--ink-rgb), 0.88);
  cursor: pointer;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  transition: background 0.2s ease, border-color 0.2s ease;
}

.nav-toggle:hover {
  background: rgba(var(--ink-rgb), 0.12);
  border-color: rgba(var(--ink-rgb), 0.28);
}

.nav-toggle:focus-visible {
  outline: 2px solid rgba(var(--ink-rgb), 0.75);
  outline-offset: 4px;
  border-radius: 0.75rem;
}

.nav-toggle-bar {
  display: block;
  width: 18px;
  height: 1.5px;
  background: currentColor;
  border-radius: 999px;
  transition: transform 0.3s ease, opacity 0.2s ease;
  transform-origin: center;
}

.nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(1) {
  transform: translateY(6.5px) rotate(45deg);
}

.nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(2) {
  opacity: 0;
  transform: scaleX(0);
}

.nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(3) {
  transform: translateY(-6.5px) rotate(-45deg);
}

/* =====================
   NAV DROPDOWN
   ===================== */

.site-nav {
  position: absolute;
  top: calc(100% + 8px);
  left: 50%; 
  right: auto; 
  width: max-content;
  min-width: 200px;
  z-index: 200;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  padding: 6px;
  margin: 0;
  border: 1px solid rgba(var(--ink-rgb), 0.16);
  border-radius: 1.1rem;
  /* Glass rather than a slab: a shade of the page's own grey at half
     strength, the page showing through as colour and light behind a heavy
     blur, with a lit top edge where a pane catches it. The blur is what
     keeps the links readable over whatever is behind; the tint only has to
     keep a white card from glaring through. Near-black at nine-tenths
     opaque was darker than anything around it and read as a hole cut in the
     page, and three-quarters still read as a panel rather than a pane. */
  background:
    linear-gradient(180deg, rgba(var(--ink-rgb), 0.1), rgba(var(--ink-rgb), 0.02) 40%),
    rgba(var(--page-raised-rgb), 0.5);
  box-shadow:
    inset 0 1px 0 rgba(var(--ink-rgb), 0.14),
    0 14px 36px rgba(var(--shade-rgb), 0.2);
  backdrop-filter: blur(28px);
  -webkit-backdrop-filter: blur(28px);
  opacity: 0;
  transform: translateX(-50%) translateY(-6px);
  pointer-events: none;
  transition: opacity 0.22s ease, transform 0.22s ease;
}

/* The saturation boost stays in light only. On the dark page it drew out
   the faint blue in the charcoal behind the menu, and the menu read bluer
   than everything around it (owner, 29 Sep 2026). */
:root[data-theme="light"] .site-nav {
  backdrop-filter: blur(28px) saturate(150%);
  -webkit-backdrop-filter: blur(28px) saturate(150%);
}

.site-nav.is-open {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  pointer-events: auto;
}

.site-nav a {
  padding: 0.78rem 1rem;
  color: rgba(var(--ink-rgb), 0.84);
  text-decoration: none;
  font-family: var(--serif);
  font-size: 1.05rem;
  text-align: center;
  border-radius: 0.72rem;
  /* Through a lighter pane — over the white contact card, say — white type
     needs a little ground of its own to stay crisp */
  text-shadow: 0 1px 3px rgba(var(--shade-rgb), 0.35);
  transition: background 0.15s ease, color 0.15s ease;
}

.site-nav a:hover {
  background: rgba(var(--ink-rgb), 0.08);
  color: rgb(var(--ink-rgb));
}

.site-nav a[aria-current="page"] {
  background: rgba(var(--ink-rgb), 0.1);
  color: rgb(var(--ink-rgb));
}

/* =====================
   HOME INTRO BLOCK
   ===================== */

.intro-block {
  margin-top: clamp(40px, 8vh, 72px);
}

.intro-block h1 {
  margin: 0;
  font-family: var(--script);
  font-size: clamp(5.4rem, 12vw, 7rem);
  line-height: 0.95;
  font-weight: 700;
}

.intro-block p {
  margin: 0.35rem 0 0;
  color: rgba(var(--ink-rgb), 0.9);
  font-size: clamp(1.2rem, 3.2vw, 1.5rem);
}

/* =====================
   DIVIDER + SECTIONS
   ===================== */

.site-divider {
  display: block;
  width: 100%;
  margin: 0 auto;
  border: 0;
  border-top: 2px solid var(--line);
}

.news-block {
  padding: 52px 0 74px;
  text-align: center;
}

.news-block h2 {
  margin: 0;
  font-family: var(--serif);
  font-size: clamp(1.5rem, 4vw, 1.8rem);  /* matches page-kicker */
  line-height: 1;
  font-weight: 700;
}

.contact-block h2 {
  margin: 0;
  font-family: var(--serif);
  font-size: clamp(2.4rem, 7vw, 4.4rem);  /* keep big, it's in the white band */
  line-height: 1;
  font-weight: 700;
}

.section-kicker {
  margin: 0 0 0.4rem;
  color: rgba(var(--ink-rgb), 0.72);
  font-family: var(--sans);
  font-size: clamp(0.9rem, 2.4vw, 1.1rem);
}

.section-kicker.dark {
  color: rgba(var(--page-rgb), 0.65);
}

.contact-block {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1.5rem;
  margin: 0 calc(50% - 50vw);
  padding: 42px max(32px, calc((100vw - 1100px) / 2)) 48px;
  background: rgb(var(--ink-rgb));
  color: var(--dark-text);
  text-align: left;
}

.contact-form {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  width: min(420px, 100%);
}

.contact-fields {
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
}

.contact-form input,
.contact-form textarea {
  width: 100%;
  padding: 0.72rem 1rem;
  border: 1px solid rgba(var(--page-rgb), 0.18);
  border-radius: 0.65rem;
  background: rgba(var(--page-rgb), 0.04);
  color: rgb(var(--page-rgb));
  font-family: var(--sans);
  font-size: 0.92rem;
  line-height: 1.4;
  outline: none;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
  -webkit-appearance: none;
}

.contact-form input:focus,
.contact-form textarea:focus {
  border-color: rgba(var(--page-rgb), 0.5);
  box-shadow: 0 0 0 3px rgba(var(--page-rgb), 0.08);
}

.contact-form textarea {
  resize: vertical;
  min-height: 110px;
}

.contact-form input::placeholder,
.contact-form textarea::placeholder {
  color: rgba(var(--page-rgb), 0.38);
}

.contact-form-footer {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

.contact-status {
  margin: 0;
  font-family: var(--sans);
  font-size: 0.85rem;
}

.contact-status.success { color: rgb(var(--form-ok-rgb)); }
.contact-status.error   { color: rgb(var(--form-error-rgb)); }

.contact-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 42px;
  padding: 0.72rem 1.2rem;
  border-radius: 999px;
  background: rgb(var(--page-rgb));
  color: rgb(var(--ink-rgb));
  font-family: var(--sans);
  font-size: 0.88rem;
  font-weight: 500;
  letter-spacing: 0.055em;
  text-transform: uppercase;
  text-decoration: none;
  box-shadow: 0 12px 30px rgba(var(--shade-rgb), 0.18);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.contact-button:hover,
.contact-button:focus-visible {
  transform: translateY(-1px);
  box-shadow: 0 16px 36px rgba(var(--shade-rgb), 0.24);
}

.site-copyright {
  margin: 0;
  padding: 24px 0 32px;
  text-align: center;
  font-family: var(--sans);
  font-size: 0.78rem;
  color: var(--faint);
  letter-spacing: 0.04em;
}

.site-copyright a { color: inherit; text-decoration: none; }
.site-copyright a:hover { text-decoration: underline; }

/* Auto / Light / Dark, at the foot of the nav menu (nav.js, theme.js). Auto
   follows the device's own setting. Ruled off from the links above it, and in
   the sans of the page's other controls rather than the links' serif. */
.theme-switch {
  display: flex;
  justify-content: center;
  margin: 6px 4px 2px;
  padding: 8px 0 2px;
  border-top: 1px solid rgba(var(--ink-rgb), 0.12);
}

.theme-switch button {
  padding: 0.34rem 0.85rem;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--muted);
  font-family: var(--sans);
  font-size: 0.74rem;
  letter-spacing: 0.04em;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

.theme-switch button:hover {
  color: rgb(var(--ink-rgb));
}

.theme-switch button[aria-pressed="true"] {
  background: rgba(var(--ink-rgb), 0.12);
  color: rgb(var(--ink-rgb));
}

/* =====================
   MOBILE
   ===================== */

@media (max-width: 720px) {
  .site-shell {
    width: min(100%, calc(100% - 24px));
  }

  .site-hero {
    min-height: 88vh;
    padding-bottom: 180px;
  }

  .header-bar {
    padding-top: 40px;
  }

  .site-logo-link {
    width: min(189px, calc(100vw - 77px));
  }

  .intro-block h1 {
    font-size: clamp(4.4rem, 20vw, 4.85rem);
  }

  .intro-block p {
    margin-top: 0.1rem;
  }

  .news-block {
    padding: 42px 0 64px;
  }

  .contact-block {
    flex-direction: column;
    align-items: flex-start;
    padding-left: 24px;
    padding-right: 24px;
  }
}
