/* ============================================================================
   Park Pilot - the whole site's stylesheet.
   ============================================================================

   One file rather than several, because there are five pages and they share
   almost everything: a header, a footer, a band, a card and a form. Split into
   sections below, in the order a page is built - tokens, reset, type, layout,
   furniture, components, pages, motion, print.

   The palette, the type and the shapes are measured off the site this replaces
   rather than invented, so the rebuild is recognisably the same site: the navy
   its headings are set in, the blue its links and rules use, the wash its wide
   bands are tinted with, the 2px keyline round its cards. What is deliberately
   different is everything that was a picture of text - the old site set its
   headings as PNGs and its icons as fixed-colour bitmaps - and everything that
   assumed a mouse.
*/

/* ---------------------------------------------------------------------------
   1. The typeface
   ---------------------------------------------------------------------------

   Mulish, self-hosted, in two subsets.

   Self-hosted and not from a font CDN, and that is a legal decision rather than
   a performance one: a German site that fetches a font from Google hands every
   visitor's IP address to a third country before they have agreed to anything,
   which a German court has already found to be a data-protection breach. The
   files are ours, served from our own origin, and the privacy notice can
   truthfully say no third party sees a visitor.

   Mulish because the original was set in Avenir, which is licensed per domain
   and cannot be self-hosted for nothing. Mulish is the closest open face to it:
   the same humanist-geometric skeleton, the same near-circular o, the same very
   light weight that the old headings depended on.

   One variable file per subset covers 300 to 900, so the light headings and the
   heavy labels are one download rather than four. `font-display: swap` shows the
   fallback immediately and swaps when the file lands - a heading nobody can read
   for 300ms is worse than a heading in the wrong face for 300ms.
*/
@font-face {
  font-family: "Mulish";
  font-style: normal;
  font-weight: 300 900;
  font-display: swap;
  src: url("../font/mulish-latin.50220d0057de.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: "Mulish";
  font-style: normal;
  font-weight: 300 900;
  font-display: swap;
  src: url("../font/mulish-latin-ext.3172231ee546.woff2") format("woff2");
  /* Where č ć ž š đ live. Without this subset the Bosnian and Croatian pages
     fall back to a system face for exactly the letters that make them Bosnian
     and Croatian, which looks like a rendering fault. */
  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;
}

/* ---------------------------------------------------------------------------
   2. Tokens
   ------------------------------------------------------------------------ */
:root {
  /* Sampled from the original: --navy is what every heading and every solid
     button is set in, --blue what links and keylines use, --sky the accent the
     bands are washed with, --sky-soft the tint behind a quiet section. */
  --navy: #113763;
  --navy-deep: #0c2748;
  --blue: #1f77db;
  --sky: #3d9be9;
  --sky-soft: #a2cdf2;
  --sky-wash: #eaf3fc;

  --ink: #1e2b38;
  --ink-soft: #55677a;
  --paper: #ffffff;
  --mist: #f4f7fa;
  --line: #dde5ee;
  --footer: #232426;

  /* One shadow, at three depths. A page with six different shadows on it reads
     as six different surfaces. */
  --lift-1: 0 1px 2px rgba(17, 55, 99, 0.06), 0 2px 8px rgba(17, 55, 99, 0.06);
  --lift-2: 0 2px 6px rgba(17, 55, 99, 0.08), 0 12px 28px rgba(17, 55, 99, 0.12);
  --lift-3: 0 4px 10px rgba(17, 55, 99, 0.1), 0 24px 48px rgba(17, 55, 99, 0.18);

  --radius: 4px;
  --radius-lg: 10px;

  /* The content column. 1160 is what the original laid its grid out on. */
  --shell: 1160px;
  --gutter: clamp(1rem, 4vw, 2.5rem);

  /* Header height, as a variable because three other things need it: the
     scroll-margin that stops an anchor landing behind the header, the hero's
     top padding, and the mobile drawer's offset. */
  --header-h: 82px;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

@media (max-width: 780px) {
  :root { --header-h: 64px; }
}

/* ---------------------------------------------------------------------------
   3. Reset and base
   ------------------------------------------------------------------------ */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  /* Every in-page link on this site lands under a sticky header without this -
     the browser scrolls the target to y=0, which is behind it. Set on the root
     so it applies to whatever the anchor happens to be. */
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 16px);
}

body {
  margin: 0;
  font-family: "Mulish", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  font-size: 17px;
  line-height: 1.65;
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* The page is a column of full-width bands; nothing may make it wider. */
  overflow-x: hidden;
}

img, svg, video { max-width: 100%; height: auto; display: block; }

a { color: var(--blue); text-decoration: none; }
a:hover { color: var(--navy); }

/* One focus ring for the whole site, and only for keyboard use. `:focus-visible`
   rather than `:focus` so a mouse click on a button does not leave a ring
   behind it - which is the reason people remove focus rings altogether, and
   removing them is what makes a site unusable without a mouse. */
:focus-visible {
  outline: 3px solid var(--sky);
  outline-offset: 2px;
  border-radius: 2px;
}

h1, h2, h3, h4 {
  margin: 0 0 0.5em;
  color: var(--navy);
  line-height: 1.18;
  font-weight: 800;
  letter-spacing: -0.01em;

  /* German compounds are the reason this is here rather than in a media query.
     "Parkraumüberwachung" is nineteen characters with no space in it, and at
     360px it is wider than the screen: the heading pushed its own column past
     the viewport and took the hero's button off the right-hand edge with it.
     `hyphens` breaks it properly where the browser has a dictionary for the
     page's `lang`, and `overflow-wrap` is the backstop where it has not. */
  overflow-wrap: break-word;
  hyphens: auto;
}

p { margin: 0 0 1em; }
p:last-child { margin-bottom: 0; }

ul { margin: 0 0 1em; padding-left: 1.25em; }

hr {
  border: 0;
  border-top: 1px solid var(--line);
  margin: 2.5rem 0;
}

/* Read by a screen reader, drawn for nobody. Used for the labels on controls
   whose only visible content is an icon or a flag. */
.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The first thing the keyboard reaches on every page. Off-screen until it has
   focus, which is the one time it is wanted. */
.skip-link {
  position: absolute;
  left: 1rem;
  top: -100px;
  z-index: 200;
  background: var(--navy);
  color: #fff;
  padding: 0.7rem 1.2rem;
  border-radius: var(--radius);
  font-weight: 700;
  transition: top 0.15s var(--ease);
}
.skip-link:focus { top: 1rem; color: #fff; }

.icon {
  width: 1.15em;
  height: 1.15em;
  flex: none;
  vertical-align: -0.2em;
  /* The sprite's weight, for the symbols that do not carry it themselves. A
     presentation attribute on a symbol beats a value inherited from the <svg>
     that references it, so a symbol whose weight has to be settable per place
     leaves the attribute off and takes it from here instead. */
  stroke-width: 1.6;
}

/* ---------------------------------------------------------------------------
   4. Layout: the shell and the bands
   ---------------------------------------------------------------------------

   The page is a stack of full-width bands, each with a centred column inside it.
   That is how the original was built and it is the right shape for the content:
   the bands are what carry the photographs, and a photograph that stops at the
   text column looks like a mistake.
*/
.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gutter);
  /* A grid or flex item's default minimum size is its content, so one long word
     inside this makes the whole column wider than its parent rather than
     wrapping. `min-width: 0` is what lets it actually be as narrow as it is
     told to be. */
  min-width: 0;
}

.shell-narrow { max-width: 820px; }

.band {
  position: relative;
  padding-block: clamp(3.5rem, 7vw, 6rem);
}
.band-tight { padding-block: clamp(2.5rem, 4vw, 3.5rem); }

.band-mist { background: var(--mist); }
.band-wash { background: var(--sky-wash); }

/* A rule of the accent blue above and below a band, which is the original's one
   piece of consistent furniture - every section on it is fenced this way. */
.band-ruled { border-block: 2px solid var(--blue); }

/* A band with a photograph behind it. The picture is set on a ::before rather
   than on the element, so the tint over it is a second layer and the two can be
   tuned separately - a wash dark enough for white text over one photograph is
   too dark over the next. */
.band-photo { color: #fff; isolation: isolate; }
.band-photo::before,
.band-photo::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
}
.band-photo::before {
  background-image: var(--photo);
  background-size: cover;
  background-position: var(--photo-pos, center);
}
/*
 * The same picture in a format half the size, where the browser understands one.
 *
 * Written as a `@supports` block rather than as an `image-set()` with a `url()`
 * fallback inside it, because a browser that does not know `image-set` throws
 * the whole declaration away - and the fallback with it, leaving a band with no
 * picture at all. This way the plain `url()` above always applies and this only
 * ever replaces it.
 *
 * `type()` is what lets the browser choose without downloading anything: it
 * takes the first entry whose format it can decode.
 *
 * The URL in the test itself is an empty `data:` URI, never fetched - a browser
 * only parses the condition. It used to name a file, which `collectstatic`
 * then tried to find and hash like any other `url()`, and failed on.
 */
@supports (background-image: image-set(url("data:image/avif;base64,") type("image/avif"))) {
  .band-photo::before {
    background-image: image-set(
      var(--photo-avif) type("image/avif"),
      var(--photo-webp) type("image/webp"),
      var(--photo) type("image/jpeg")
    );
  }
}
/*
 * The wash over the photograph, and how much of it is left showing.
 *
 * This used to be the brand blue at 86-94%, which meant the photograph
 * underneath was a texture and nothing more - there was no point in choosing
 * one. It is now 62-74%, so roughly three times as much of the picture comes
 * through, and the photographs are worth looking at again.
 *
 * The colours changed with the opacity, and that is the part worth explaining.
 * Simply turning 0.86 down to 0.62 would have taken the white text with it: the
 * top stop was `--blue` (#1F77DB), which is light enough that white on it was
 * only about 4.2:1 even at the old opacity, and thinning it made that worse. The
 * stops are now a darker blue and a deeper navy, which buys back more contrast
 * than the thinner wash costs - white text over the brightest photograph on the
 * site measures about 4.9:1 here against 4.2:1 before.
 *
 * So this is not a trade of legibility for a nicer picture. It is both, and the
 * numbers are the reason the colours are not simply `--blue` and `--navy`.
 */
.band-photo::after {
  background: linear-gradient(
    180deg,
    rgba(26, 95, 175, var(--tint-top, 0.62)),
    rgba(12, 39, 72, var(--tint-bottom, 0.74))
  );
}
.band-photo h1, .band-photo h2, .band-photo h3 { color: #fff; }
.band-photo a { color: #fff; }

/* The band title the original used everywhere: one light line, centred, wide
   letter spacing, on the blue wash. It is the site's loudest typographic idea
   and the thing that makes a scroll down the page feel like one document. */
.band-title {
  font-weight: 300;
  font-size: clamp(1.6rem, 3.4vw, 2.6rem);
  letter-spacing: 0.02em;
  text-align: center;
  margin: 0;
  color: #fff;
}

/* The same band title on a light ground. `.band-title` is white because every
   band that carried one was a photograph; the front page's offer band is not one
   any more - see the note above it - and white on the wash is unreadable. The
   colour is the only thing that changes: the weight, the size and the letter
   spacing are what make it that band's title rather than an ordinary h2. */
.band-title-dark { color: var(--navy); }

/* ---------------------------------------------------------------------------
   The claim card
   ---------------------------------------------------------------------------

   The front page's one rhetorical question, on a card rather than loose on the
   band. It is the only deep-coloured surface between the hero and the closing
   band, which is what makes it the thing the eye stops on while scrolling past
   two light sections.

   Three layers, and each is doing one job: the gradient is the ground, the
   `::before` bloom keeps that gradient from reading as a flat rectangle at large
   sizes, and the `::after` hairline is the inner edge that stops the rounded
   corner looking soft. All three are painted, so nothing here is a request.

   `overflow: hidden` is what clips the bloom to the rounded corner; without it
   the circle escapes the card. */
.claim-card {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  max-width: 900px;
  margin-inline: auto;
  padding: clamp(2rem, 5vw, 3.25rem) clamp(1.5rem, 5vw, 3.25rem);
  border-radius: clamp(16px, 2.5vw, 26px);
  text-align: center;
  color: #fff;
  background: linear-gradient(135deg, var(--navy-deep) 0%, var(--navy) 42%, #1c62b4 100%);
  box-shadow: var(--lift-3);
}
.claim-card::before,
.claim-card::after { content: ""; position: absolute; pointer-events: none; }
.claim-card::before {
  top: -55%;
  right: -12%;
  width: 62%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(61, 155, 233, 0.55), rgba(61, 155, 233, 0) 68%);
  z-index: -1;
}
.claim-card::after {
  inset: clamp(8px, 1.2vw, 14px);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: clamp(10px, 1.8vw, 16px);
}

/* `.h-call` is `--sky` and `.lede-call` is `--blue`; both are chosen for a white
   band and neither survives being put on a navy one. White and the soft tint are
   the same pair every `.band-photo` uses, so the card reads as part of the same
   system rather than as a fourth colour scheme. */
.claim-card .h-call { color: #fff; }
.claim-card .lede-call { color: var(--sky-soft); }

.section-head {
  max-width: 780px;
  margin-bottom: clamp(2rem, 4vw, 3rem);
}
.section-head.centre { margin-inline: auto; text-align: center; }

.eyebrow {
  display: block;
  font-size: 0.82rem;
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sky);
  margin-bottom: 0.75rem;
}
.band-photo .eyebrow { color: var(--sky-soft); }

h1 { font-size: clamp(2rem, 5vw, 3.2rem); }
h2 { font-size: clamp(1.55rem, 3.4vw, 2.35rem); }
h3 { font-size: clamp(1.15rem, 2vw, 1.4rem); }

.lede {
  font-size: clamp(1.05rem, 1.6vw, 1.2rem);
  color: var(--ink-soft);
  font-weight: 400;
}
.band-photo .lede { color: rgba(255, 255, 255, 0.9); }

.muted { color: var(--ink-soft); }
.tiny { font-size: 0.85rem; }

/* ---------------------------------------------------------------------------
   5. The header
   ---------------------------------------------------------------------------

   Sticky, which the original was too, and the one thing it is asked to hold that
   the original did not have: a language bar on the right.

   `position: sticky` rather than `fixed`, so the header takes up its own room in
   the flow and nothing has to be padded down by hand to clear it.
*/
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: rgba(255, 255, 255, 0.94);
  backdrop-filter: saturate(1.6) blur(10px);
  border-bottom: 2px solid var(--blue);
  transition: box-shadow 0.25s var(--ease), background 0.25s var(--ease);
}
/* Set by `header.js` once the page has scrolled at all. The header is flat while
   the hero is behind it and lifts off the page once content is running under it
   - which is the cue that says the bar is floating rather than painted on. */
.site-header.is-stuck {
  box-shadow: var(--lift-2);
  background: rgba(255, 255, 255, 0.98);
}

/*
 * The bar gets a wider column than the text does.
 *
 * `--shell` is 1160px because that is the measure the original laid its content
 * out on, and it is the right width for a paragraph. It is not the right width
 * for a row of five German navigation labels, a wordmark and four flags: that
 * row measures about 1230px in German however tightly it is set, so inside a
 * 1160px shell it overflowed at *every* viewport width and simply spilled into
 * the page margin, where `overflow-x: hidden` on the body quietly swallowed it.
 *
 * A sticky bar spanning wider than the text under it is normal and reads as
 * deliberate; a bar whose last item is clipped does not. The two numbers below
 * are tied to each other - the drawer breakpoint is this width plus the two
 * gutters - so if either moves the other has to.
 */
.site-header .shell { max-width: 1320px; }

.header-inner {
  display: flex;
  align-items: center;
  gap: clamp(0.75rem, 2vw, 2rem);
  min-height: var(--header-h);
}

.brand {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  flex: none;
  color: var(--navy);
}
.brand:hover { color: var(--navy); }
.brand img { width: auto; height: 40px; }
.brand-text { display: flex; flex-direction: column; line-height: 1.05; }
.brand-name {
  font-weight: 800;
  letter-spacing: 0.06em;
  font-size: 1.02rem;
  text-transform: uppercase;
}
.brand-tag {
  font-size: 0.68rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--sky);
  font-weight: 700;
  white-space: nowrap;
}

/* "Parkraum-Management" under the wordmark is the least load-bearing thing in
   the header, so it is the first to go when the row is tight. The name stays. */
@media (max-width: 1220px) and (min-width: 1121px) {
  .brand-tag { display: none; }
}

.site-nav {
  display: flex;
  align-items: center;
  gap: 0.2rem;
  margin-left: auto;
  /* A flex item's default minimum size is its content, which is what let the
     row grow past the header rather than being confined by it. With this, an
     overflow becomes visible in layout - which is how it gets noticed - rather
     than silently spilling out of the right-hand side. */
  min-width: 0;
}

/*
 * The nav items are drawn as buttons: a keyline, a radius, a pale ground.
 *
 * They were bare words with an underline that only appeared on hover, which
 * left the bar reading as a line of text rather than as a set of controls -
 * there was nothing to say where one target ended and the next began until you
 * were already pointing at it. A visible border is the cheapest fix and it also
 * gives the current page somewhere to be marked that survives the item being
 * the widest thing in the row.
 *
 * The old growing underline is gone rather than kept alongside: an outlined
 * button that also grows a rule under itself on hover is two hover effects for
 * one target.
 */
.site-nav a {
  position: relative;
  display: block;
  padding: 0.5rem 0.85rem;
  color: var(--navy);
  font-weight: 700;
  font-size: 0.95rem;
  border: 1.5px solid var(--line);
  border-radius: 999px;
  background: #fff;
  /* Buttons in a row have to be one line each. "Kontakt / Falschparker" is the
     only label long enough to wrap, and one wrapped item made itself taller than
     the four beside it - which reads as a fault rather than as a long word. The
     drawer breakpoint below is set from the width this needs. */
  white-space: nowrap;
  transition: color 0.18s var(--ease), background 0.18s var(--ease),
    border-color 0.18s var(--ease), box-shadow 0.18s var(--ease);
}
.site-nav a:hover {
  background: var(--sky-wash);
  border-color: var(--sky-soft);
  color: var(--blue);
}
/* The page being read is filled rather than outlined - the one state in the bar
   that has to be legible at a glance from across the row. */
.site-nav a[aria-current="page"] {
  color: #fff;
  background: var(--blue);
  border-color: var(--blue);
  box-shadow: var(--lift-1);
}
.site-nav a[aria-current="page"]:hover {
  background: var(--navy);
  border-color: var(--navy);
  color: #fff;
}

/* The call to action in the bar. Solid navy, because it is the one thing in the
   header that is not navigation - and it stays solid even when the page it
   points at is the one being read, so it never borrows the current-page fill. */
.site-nav .nav-cta,
.site-nav .nav-cta[aria-current="page"] {
  margin-left: 0.4rem;
  background: var(--navy);
  border-color: var(--navy);
  color: #fff;
  padding-inline: 1.05rem;
}
.site-nav .nav-cta:hover,
.site-nav .nav-cta[aria-current="page"]:hover {
  background: var(--blue);
  border-color: var(--blue);
  color: #fff;
}

/* --- The language bar ---------------------------------------------------- */

.lang-options {
  display: flex;
  align-items: center;
  gap: 0.15rem;
}

.lang-bar {
  display: flex;
  align-items: center;
  gap: 0.15rem;
  margin-left: 0.6rem;
  padding-left: 0.9rem;
  border-left: 1px solid var(--line);
}

.lang-option {
  display: inline-flex;
  align-items: center;
  gap: 0.34rem;
  border: 1px solid transparent;
  background: none;
  padding: 0.3rem 0.42rem;
  border-radius: var(--radius);
  cursor: pointer;
  font: inherit;
  font-size: 0.78rem;
  font-weight: 800;
  letter-spacing: 0.05em;
  color: var(--ink-soft);
  transition: background 0.18s var(--ease), color 0.18s var(--ease),
    border-color 0.18s var(--ease);
}
.lang-option:hover { background: var(--sky-wash); color: var(--navy); }
.lang-option.is-active {
  color: var(--navy);
  border-color: var(--sky-soft);
  background: var(--sky-wash);
}

/* The flag. A fixed 20x14 box with a hairline round it, because several of these
   flags are mostly white and would otherwise bleed into the header. */
.flag {
  width: 21px;
  height: 15px;
  border-radius: 2px;
  box-shadow: 0 0 0 1px rgba(17, 55, 99, 0.18);
  flex: none;
  /* Grey until it is the current language or hovered: four saturated flags in a
     row shout louder than the navigation next to them. */
  filter: saturate(0.25) opacity(0.75);
  transition: filter 0.2s var(--ease), transform 0.2s var(--ease);
}
.lang-option:hover .flag,
.lang-option.is-active .flag { filter: none; transform: translateY(-1px); }

/* --- The mobile drawer ---------------------------------------------------- */

.nav-toggle {
  display: none;
  margin-left: auto;
  align-items: center;
  justify-content: center;
  width: 42px; height: 42px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: #fff;
  color: var(--navy);
  cursor: pointer;
}
.nav-toggle .icon { width: 22px; height: 22px; }
.nav-toggle .icon-close { display: none; }
.nav-toggle[aria-expanded="true"] .icon-menu { display: none; }
.nav-toggle[aria-expanded="true"] .icon-close { display: block; }

/* --- Where German runs out of room ---------------------------------------
 *
 * The navigation is a row of labels, and the German labels are half as long
 * again as the English ones: "Parkraumüberwachung" and "Geschäftsanfragen"
 * against "Parking enforcement" and "For businesses". Below about 1120px the
 * German row no longer fits, and because a flex row overflows rather than
 * wrapping, what it pushed off the right-hand edge was the language bar - the
 * flags ended up half outside the window with their letters cut off. English
 * showed nothing wrong at the same width, which is why it survived a first pass.
 *
 * Two steps, in this order:
 *
 * 1. Under 1400px the items are set tighter. The code has already gone from the
 *    language buttons at every width - see the note below - so this is the only
 *    step left before the drawer.
 * 2. Under 1320px the row is given up entirely for the drawer. That is the
 *    header's own shell width above, and below it there is no longer room for
 *    the German row even set as tightly as step 1 sets it.
 *
 * Both moved out when the bar grew. There are five items in it now rather than
 * four, and the fourth is "Kontakt / Falschparker" rather than "Kontakt": in
 * German the row measures about 1220px of labels, buttons and gaps before the
 * brand and the flags are counted, which is why the drawer now starts 120px
 * earlier than it did. `tools/check_layout.mjs` is what says whether a number
 * like that is right, and it is worth re-running after any label changes.
 */
/* So the header shows flags alone, at every width. That is what a flag is for -
   recognised before it is read, which is exactly what a language picker has to
   be for somebody who cannot read the language currently on screen - and it
   makes the bar immune to how long a fifth language's labels turn out to be.
   The code stays in the button's accessible name and comes back in the drawer,
   where there is room and where a flag on its own is more ambiguous. */
.lang-code { display: none; }

@media (max-width: 1400px) {
  .lang-option { padding: 0.34rem 0.4rem; }
  .site-nav { gap: 0.15rem; }
  .site-nav a { padding-inline: 0.62rem; font-size: 0.9rem; }
  .lang-bar { margin-left: 0.4rem; padding-left: 0.6rem; }
}

@media (max-width: 1320px) {
  .nav-toggle { display: inline-flex; }

  .site-nav {
    position: fixed;
    inset: var(--header-h) 0 auto 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    margin: 0;
    padding: 0.6rem var(--gutter) 1.4rem;
    background: #fff;
    border-bottom: 2px solid var(--blue);
    box-shadow: var(--lift-2);
    /* Closed by default, and closed by being *scaled away* rather than
       display:none - so it can be animated, and so the links inside it are
       still there for a search engine reading the markup. `visibility` is what
       takes them out of the tab order while it is shut. */
    transform-origin: top;
    transform: translateY(-8px);
    opacity: 0;
    visibility: hidden;
    transition: transform 0.22s var(--ease), opacity 0.22s var(--ease),
      visibility 0s linear 0.22s;
  }
  .site-nav.is-open {
    transform: none;
    opacity: 1;
    visibility: visible;
    transition-delay: 0s;
  }
  /* Full-width rows in the drawer, not pills: five outlined buttons stacked
     down a panel reads as five boxes rather than as one list, and the row is
     already separated from its neighbours by being a row. The current page
     keeps its fill, which is the one thing worth carrying over. */
  .site-nav a {
    padding: 0.8rem 0.7rem;
    font-size: 1.05rem;
    border-color: transparent;
    border-radius: var(--radius);
    background: transparent;
    white-space: normal;
  }
  .site-nav a:hover { border-color: transparent; }
  .site-nav .nav-cta,
  .site-nav .nav-cta[aria-current="page"] {
    margin: 0.7rem 0 0;
    text-align: center;
    padding-block: 0.85rem;
  }
  .lang-bar {
    margin: 0.9rem 0 0;
    padding: 0.9rem 0 0;
    border-left: 0;
    border-top: 1px solid var(--line);
    justify-content: flex-start;
    flex-wrap: wrap;
  }
  /* In the drawer there is room again, so the codes come back - and inside a
     panel rather than a tight row, a flag on its own is more ambiguous than it
     is in the header. */
  .lang-code { display: inline; }
  .lang-option { padding: 0.45rem 0.6rem; font-size: 0.85rem; }
}

/* ---------------------------------------------------------------------------
   6. Buttons
   ------------------------------------------------------------------------ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.55rem;
  padding: 0.85rem 1.7rem;
  border: 2px solid transparent;
  border-radius: var(--radius);
  font-weight: 800;
  font-size: 0.95rem;
  letter-spacing: 0.02em;
  cursor: pointer;
  text-align: center;
  transition: background 0.2s var(--ease), color 0.2s var(--ease),
    border-color 0.2s var(--ease), transform 0.2s var(--ease),
    box-shadow 0.2s var(--ease);
}
/* A button that cannot be pressed has to look it. The attribute already stops
   the press - the studio disables "Erzeugen" on a document whose fields are not
   filled in yet - but a button that refuses silently while looking exactly like
   the one beside it reads as a broken page rather than as a rule. */
.btn:disabled,
.btn[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
  box-shadow: none;
  transform: none;
}
.btn:disabled:hover .icon,
.btn[disabled]:hover .icon { transform: none; }

/* The arrow slides forward on hover. It is the only motion on a button, and it
   points at what pressing it does. */
.btn .icon { transition: transform 0.2s var(--ease); }
.btn:hover .icon { transform: translateX(3px); }
.btn:active { transform: translateY(1px); }

.btn-primary { background: var(--navy); color: #fff; }
.btn-primary:hover { background: var(--blue); color: #fff; box-shadow: var(--lift-2); }

.btn-accent { background: var(--blue); color: #fff; }
.btn-accent:hover { background: var(--navy); color: #fff; box-shadow: var(--lift-2); }

.btn-outline {
  border-color: var(--navy);
  color: var(--navy);
  background: transparent;
}
.btn-outline:hover { background: var(--navy); color: #fff; }

/* On a photograph: white keyline, filling white on hover. */
.btn-ghost {
  border-color: rgba(255, 255, 255, 0.85);
  color: #fff;
  background: rgba(255, 255, 255, 0.06);
}
.btn-ghost:hover { background: #fff; color: var(--navy); border-color: #fff; }

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.9rem;
  margin-top: 1.75rem;
}
.btn-row.centre { justify-content: center; }

/* A link that behaves like a button but is drawn as text with an arrow - the
   original's "Direkt anfragen ➟". */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-weight: 800;
  color: var(--blue);
}
.link-arrow .icon { transition: transform 0.2s var(--ease); }
.link-arrow:hover .icon { transform: translateX(4px); }

/* ---------------------------------------------------------------------------
   7. Grids and cards
   ------------------------------------------------------------------------ */
.grid { display: grid; gap: clamp(1.1rem, 2.4vw, 1.9rem); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(270px, 1fr)); }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(215px, 1fr)); }

/*
 * The six promise cards under the hero: a photograph, a wash, white text.
 *
 * Three used to be four-pixel-cornered rectangles with a hard two-pixel sky
 * keyline round them, which is what the original's baked-in images looked like.
 * There are six now - the three sign plates lead, then the three the original
 * promised - and at six the hard keyline read as a grid of boxes rather than as
 * a row of cards. So: an 18px radius, a keyline softened to a translucent white
 * hairline, and a deeper lift. The photograph and the wash behind them are
 * unchanged; the frame around them is not.
 */
.promise {
  position: relative;
  display: flex;
  flex-direction: column;
  /* The figure, the title and the sentence read as one centred block, so the
     group is centred in the card rather than pushed to its foot. */
  justify-content: center;
  align-items: center;
  text-align: center;
  min-height: 300px;
  padding: 2.1rem 1.6rem;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 18px;
  color: #fff;
  overflow: hidden;
  isolation: isolate;
  box-shadow: var(--lift-2);
  transition: transform 0.3s var(--ease), box-shadow 0.3s var(--ease),
    border-color 0.3s var(--ease);
}
.promise::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -2;
  background: url("../img/photo/card-lot-1280.a2af316a69c8.jpg") center / cover;
  transform: scale(1.02);
  transition: transform 0.5s var(--ease);
}
/* One photograph behind all three cards, under a heavy wash. In a smaller
   format where the browser understands one - and *after* the rule above, because
   the two have the same specificity and source order is what decides. */
@supports (background-image: image-set(url("data:image/avif;base64,") type("image/avif"))) {
  .promise::before {
    background-image: image-set(
      url("../img/photo/card-lot-1280.0a5a2a9ebdee.avif") type("image/avif"),
      url("../img/photo/card-lot-1280.05ca9aab5aa1.webp") type("image/webp"),
      url("../img/photo/card-lot-1280.a2af316a69c8.jpg") type("image/jpeg")
    );
  }
}

.promise::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    170deg,
    rgba(17, 55, 99, 0.62) 0%,
    rgba(17, 55, 99, 0.86) 60%,
    rgba(12, 39, 72, 0.94) 100%
  );
  transition: opacity 0.35s var(--ease);
}
.promise:hover { transform: translateY(-6px); box-shadow: var(--lift-3); border-color: rgba(255, 255, 255, 0.7); }
.promise:hover::before { transform: scale(1.08); }
.promise:hover::after { opacity: 0.86; }

/* The glyph leads, above the words, and it is by a long way the largest icon on
   the site. These cards are the first thing under the hero and the glyph is what
   carries them at a glance; at 58px it still read as decoration beside the
   heading rather than as the subject of the card, and these are solid artwork
   rather than line icons, so they take the size without thinning out. */
.promise .promise-icon {
  width: 84px;
  height: 84px;
  flex: none;
  margin: 0 auto 1.15rem;
  color: #fff;
  opacity: 1;
}

/*
 * The sign plates on the first three cards.
 *
 * A photograph of a white metal plate, not an icon: it keeps its own colours
 * and needs no wash behind it, because white-and-blue on the dark card is
 * already the strongest contrast on the page. Sized by height rather than by
 * width, so the three plates - which are photographed at slightly different
 * widths - line up along their tops and bottoms instead of along their edges.
 */
.promise-figure {
  flex: none;
  margin-bottom: 1.15rem;
  filter: drop-shadow(0 6px 14px rgba(6, 22, 42, 0.45));
}
.promise-figure img {
  height: clamp(132px, 17vw, 156px);
  width: auto;
  display: block;
}
/* The plate is the tallest thing that goes in one of these, so the cards
   carrying one are taller. Setting it here rather than on `.promise` keeps the
   three glyph cards from growing a hundred pixels of empty space to match. */
.promise-sign { min-height: 360px; }
.promise-title {
  color: #fff;
  font-weight: 700;
  margin-bottom: 0.45rem;
}
.promise p {
  font-size: 0.92rem;
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.86);
  margin: 0;
  /* Held to a comfortable measure so a centred paragraph does not run the full
     width of the card - centred text is much harder to read long. */
  max-width: 34ch;
}

/* The plain card: white, hairline, lifts on hover. Everything on the site that
   is a box and is not a promise card is one of these. */
.card {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: clamp(1.4rem, 2.5vw, 2rem);
  box-shadow: var(--lift-1);
  transition: transform 0.25s var(--ease), box-shadow 0.25s var(--ease),
    border-color 0.25s var(--ease);
}
.card:hover {
  transform: translateY(-4px);
  box-shadow: var(--lift-2);
  border-color: var(--sky-soft);
}
.card h3 { margin-bottom: 0.6rem; }

.card .card-icon {
  width: 40px;
  height: 40px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--sky-wash);
  color: var(--blue);
  transition: background 0.25s var(--ease), color 0.25s var(--ease);
}

/* A row of cards that are read at a glance rather than in full: the icon leads,
   above the words, at the same weight as the promise cards so the two rows on
   the front page look like one idea. */
.card-centred {
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
}
/*
 * The glyph in the small inline disc.
 *
 * This has to come *before* the centred rule below and not after it, which is
 * where it used to sit. `.card .card-icon .icon` and
 * `.card-centred .card-icon .icon` are both three classes, so they have equal
 * specificity and nothing but source order separates them - and with this one
 * last it won, silently, on the centred cards too. Those were written to draw
 * their glyph at 38px and had been drawing it at 22 ever since, which is why the
 * discs on the front page looked like discs with a mark in them rather than
 * icons in a tint.
 *
 * The general case first and the specific case after it is the order that makes
 * a cascade work. Anything added here that only applies to one kind of card
 * belongs below, not above.
 */
.card .card-icon .icon { width: 22px; height: 22px; }

.card-centred .card-icon {
  width: 78px;
  height: 78px;
  margin-bottom: 1.1rem;
}
/*
 * The glyph nearly fills its disc rather than floating in the middle of it.
 *
 * It asked for 38px inside 78px and was in fact drawing 22 - see the note above
 * for why. Either way it was a disc with a mark in the middle of it: a ring of
 * pale blue several times wider than the drawing inside it, so the disc read as
 * the subject and the glyph as a detail of it.
 *
 * 64px, and the number comes from the ink rather than from the box. None of
 * these glyphs fills its own 24-unit square: the widest is 18 units across and
 * `car-p` is only 10.6 tall. So sizing the box to the circle leaves the drawing
 * far smaller than it looks like it should be - at a 54px box the widest ink
 * measured 40px inside a 78px disc, barely half the diameter, which is the shape
 * of the complaint that this is still small.
 *
 * At 64 the ink runs 48px across and the corners of its bounding box sit at
 * about 0.85 of the radius, which leaves room for the stroke - `getBBox` reports
 * the path, not the drawn line, and a 2-unit stroke at this size adds nearly 3px
 * on each edge. Going much past this puts the top-right of `chart-up`'s arrow
 * through the disc.
 *
 * The weight goes up with the size. At 64px a 1.6 stroke draws 4.3px and reads
 * as a thin outline around a large shape rather than as a drawing of one; 2
 * gives back the density it has at text size. That is settable at all only
 * because these three symbols leave `stroke-width` off - see the note at the top
 * of core/icons.svg.
 */
.card-centred .card-icon .icon {
  width: 64px;
  height: 64px;
  stroke-width: 2;
}
.card-centred p { max-width: 34ch; }

/* The one card that is a details block rather than one of a row keeps its icon
   on the heading's line. */
.card-title-inline {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
.card-title-inline .card-icon { margin-bottom: 0; }
.card:hover .card-icon { background: var(--blue); color: #fff; }

/* The sector cards - airports, retail, healthcare, offices - on the blue band.
   Divided by hairlines rather than boxed, which is how the original drew them. */
.sector {
  display: flex;
  flex-direction: column;
  padding: 1.6rem 1.2rem;
  border-radius: var(--radius);
  transition: background 0.25s var(--ease), transform 0.25s var(--ease);
}
.sector:hover { background: rgba(255, 255, 255, 0.1); transform: translateY(-4px); }
/* Larger than the rest of the icon set, and centred in its column. These four
   are the only place an icon is the thing being looked at rather than a marker
   beside a label, and at 52px on a wide blue band they read as small. */
.sector .sector-icon {
  width: 64px;
  height: 64px;
  color: #fff;
  margin: 0 auto 1.1rem;
  display: block;
}
.sector h3 {
  font-weight: 300;
  font-size: 1.22rem;
  color: #fff;
  line-height: 1.25;
  /* Centred under the centred icon. The list below stays left-aligned: it is a
     list of four things to read down, and centring those makes them hard to
     scan. */
  text-align: center;
}
.sector h3 strong { display: block; font-weight: 700; }
.sector ul { list-style: none; padding: 0; margin: 0 0 1.4rem; }
.sector li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.92rem;
  padding: 0.24rem 0;
  color: rgba(255, 255, 255, 0.92);
}
.sector li .icon { width: 0.85em; height: 0.85em; color: var(--sky-soft); }
.sector .link-arrow { margin-top: auto; color: #fff; }
.sector .link-arrow:hover { color: var(--sky-soft); }

/* ---------------------------------------------------------------------------
   8. Lists that are really content
   ------------------------------------------------------------------------ */

/* The tick list. The original set these as a checkmark PNG beside each line;
   here the tick is drawn, so it takes the colour of whatever it sits on. */
.ticks { list-style: none; padding: 0; margin: 0; }
.ticks li {
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  padding: 0.42rem 0;
  line-height: 1.5;
}
.ticks li .icon {
  color: var(--sky);
  width: 1.05em;
  height: 1.05em;
  margin-top: 0.25em;
}
.band-photo .ticks li .icon { color: var(--sky-soft); }

/* A tick list where each line is its own bordered row - used where the list is
   the section rather than a note inside one. */
.ticks-boxed li {
  border-bottom: 1px solid var(--line);
  padding: 0.7rem 0;
}
.ticks-boxed li:last-child { border-bottom: 0; }
.band-photo .ticks-boxed li { border-color: rgba(255, 255, 255, 0.22); }

/* The caret list: the original's small blue triangle bullet. */
.carets { list-style: none; padding: 0; margin: 0; }
.carets li {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.34rem 0;
}
.carets li .icon {
  color: var(--blue);
  width: 0.8em;
  height: 0.8em;
  margin-top: 0.42em;
}

/* Two columns of split content - a picture beside a block of text. Reversed by
   a modifier rather than by `direction`, so the source order stays the reading
   order for a screen reader. */
.split {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: clamp(1.8rem, 4vw, 3.5rem);
  align-items: center;
}
.split-media img { border-radius: var(--radius-lg); box-shadow: var(--lift-2); }
@media (min-width: 860px) {
  .split-flip .split-media { order: 2; }
}

/* Two columns aligned at the top rather than centred against each other. For a
   picture beside a paragraph, centred is right; for two columns of text of very
   different lengths - the contact page's details beside its form - it leaves the
   shorter one floating half a screen down from the heading opposite it. */
.split-top { align-items: start; }

/* The framed picture the original used: a blue rectangle offset behind the
   image. Drawn with a pseudo-element rather than a second image. */
.framed { position: relative; }
.framed::before {
  content: "";
  position: absolute;
  inset: -18px -18px 18px 18px;
  border: 2px solid var(--blue);
  border-radius: var(--radius);
  z-index: -1;
  transition: inset 0.35s var(--ease);
}
.framed:hover::before { inset: -24px -24px 12px 12px; }
.framed img { position: relative; }

/* ---------------------------------------------------------------------------
   9. The hero
   ------------------------------------------------------------------------ */
.hero {
  position: relative;
  display: grid;
  /* `minmax(0, 1fr)` rather than the implicit `auto`: an auto track is sized to
     its content's minimum, which for a single long German word is wider than
     the screen. This is the other half of the fix described on `h1` above -
     without it the heading wraps and the track is still too wide. */
  grid-template-columns: minmax(0, 1fr);
  place-items: center;
  text-align: center;
  min-height: min(76vh, 620px);
  padding-block: clamp(3.5rem, 9vw, 6.5rem);
  color: #fff;
  isolation: isolate;
  overflow: hidden;
}
.hero::before,
.hero::after { content: ""; position: absolute; inset: 0; z-index: -1; }
.hero::before {
  background: var(--hero-photo) center / cover;
  background-repeat: no-repeat;
  /* A slow drift, so the hero is not a still photograph. Barely perceptible on
     purpose: a hero that moves visibly is a hero nobody reads. */
  animation: hero-drift 26s var(--ease) infinite alternate;
}
/* Thinned to match the bands - see the note on `.band-photo::after`. The hero
   carries the largest photograph on the site and used to show almost none of
   it. The stops are the same darker pair the bands use, so what is lost in
   opacity comes back as contrast. */
.hero::after {
  background: linear-gradient(
    165deg,
    rgba(26, 95, 175, 0.6),
    rgba(12, 39, 72, 0.76)
  );
}
@keyframes hero-drift {
  from { transform: scale(1.06) translate3d(0, 0, 0); }
  to { transform: scale(1.14) translate3d(-1.2%, -1.4%, 0); }
}


.hero-mark { width: 84px; height: 84px; margin: 0 auto 1.4rem; }
.hero h1 {
  color: #fff;
  font-weight: 300;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  font-size: clamp(1.8rem, 5.2vw, 3.4rem);
  margin-bottom: 0.5rem;
}
.hero .hero-sub {
  font-weight: 300;
  font-size: clamp(1.05rem, 2.3vw, 1.6rem);
  color: rgba(255, 255, 255, 0.94);
  margin: 0 auto;
  max-width: 40ch;
}

/* The three promise cards overlap the foot of the hero, as they did on the
   original. Pulled up with a negative margin on the section that holds them. */
.hero-cards {
  position: relative;
  z-index: 2;
  margin-top: clamp(-5rem, -6vw, -3rem);
}

/* A page header for the four inner pages: the same photographic band, shorter. */
.page-head {
  position: relative;
  padding-block: clamp(2.5rem, 6vw, 4.5rem);
  text-align: center;
  color: #fff;
  isolation: isolate;
  overflow: hidden;
}
.page-head::before,
.page-head::after { content: ""; position: absolute; inset: 0; z-index: -1; }
.page-head::before { background: var(--hero-photo) center / cover; }
/* A shade heavier than the hero's: a page head is a short band with a heading
   and one line of lede in it, so there is less white text to carry the block and
   more of it sits over whatever happens to be in the middle of the photograph. */
.page-head::after {
  background: linear-gradient(165deg, rgba(26, 95, 175, 0.66), rgba(12, 39, 72, 0.8));
}
/* The hero and the inner pages' headers, in a smaller format where the browser
   understands one.
 *
 * Placed after both rules deliberately. `.hero::before` and `.page-head::before`
 * both set the `background` *shorthand*, which resets `background-image` - so
 * this block sitting above either of them is overridden and thrown away, with no
 * warning and no visible difference except that every inner page quietly went on
 * downloading the JPEG. It did exactly that until the page was weighed.
 */
@supports (background-image: image-set(url("data:image/avif;base64,") type("image/avif"))) {
  .hero::before,
  .page-head::before {
    background-image: image-set(
      var(--hero-avif) type("image/avif"),
      var(--hero-webp) type("image/webp"),
      var(--hero-photo) type("image/jpeg")
    );
  }
}

.page-head h1 {
  color: #fff;
  font-weight: 300;
  font-size: clamp(1.9rem, 4.6vw, 3rem);
  letter-spacing: 0.04em;
  margin-bottom: 0.4rem;
}
/* The lede's own colour is the body-text grey, which on a photograph is a line
   nobody can read - it has to be restated here, because `.page-head` is not
   `.band-photo` and does not inherit its overrides. */
.page-head .lede {
  max-width: 62ch;
  margin-inline: auto;
  color: rgba(255, 255, 255, 0.92);
}
.page-head .eyebrow { color: var(--sky-soft); }

/* ---------------------------------------------------------------------------
   10. Statistics, quotes and the small furniture
   ------------------------------------------------------------------------ */
.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: clamp(1rem, 3vw, 2.5rem);
  text-align: center;
}
/* The stat's own colours are the ones for a light ground, and the band-photo
   overrides below are what put it on a dark one. It used to be the other way
   round - white hard-coded here, with no light variant at all - which meant the
   row could only ever live on a photograph. That is not a property of a stat;
   it was a property of the one band that happened to use it, and it is why
   moving the business page's pitch off its photograph needed this before it
   needed anything in the template. */
.stat .stat-icon { width: 48px; height: 48px; margin: 0 auto 0.8rem; color: var(--sky); }
.stat h3 { color: var(--navy); font-weight: 300; font-size: 1.3rem; margin-bottom: 0.4rem; }
.stat p { font-size: 0.9rem; color: var(--ink-soft); margin: 0; }
.band-photo .stat .stat-icon { color: #fff; }
.band-photo .stat h3 { color: #fff; }
.band-photo .stat p { color: rgba(255, 255, 255, 0.85); }

.quote {
  border-left: 3px solid var(--blue);
  padding: 0.4rem 0 0.4rem 1.6rem;
  font-size: clamp(1.05rem, 2vw, 1.25rem);
  font-weight: 300;
  color: var(--navy);
  font-style: normal;
  margin: 0;
}
.quote footer {
  margin-top: 0.9rem;
  font-size: 0.85rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--sky);
}

/* The signature block the original ended two of its sections with. */
.signature {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  margin-top: 1.8rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-size: 0.85rem;
  color: var(--navy);
}
.signature img { width: 34px; height: 34px; }
.band-photo .signature { color: #fff; }

/* ---------------------------------------------------------------------------
   11. Forms
   ------------------------------------------------------------------------ */
.form-card {
  background: #fff;
  border: 1px solid var(--line);
  border-top: 3px solid var(--blue);
  border-radius: var(--radius-lg);
  padding: clamp(1.5rem, 3.5vw, 2.5rem);
  box-shadow: var(--lift-2);
}

.field { margin-bottom: 1.1rem; }
.field label {
  display: block;
  font-weight: 800;
  font-size: 0.82rem;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--navy);
  margin-bottom: 0.4rem;
}
.field .req { color: var(--blue); }

.field input,
.field textarea,
.field select {
  width: 100%;
  font: inherit;
  font-size: 1rem;
  color: var(--ink);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.75rem 0.9rem;
  transition: border-color 0.18s var(--ease), box-shadow 0.18s var(--ease);
}
.field textarea { min-height: 150px; resize: vertical; }
.field input::placeholder,
.field textarea::placeholder { color: #9aabbd; }
.field input:hover,
.field textarea:hover { border-color: var(--sky-soft); }
.field input:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--blue);
  box-shadow: 0 0 0 3px rgba(61, 155, 233, 0.22);
}
.field.has-error input,
.field.has-error textarea { border-color: #c0392b; }
.field .help { font-size: 0.82rem; color: var(--ink-soft); margin-top: 0.35rem; }
.field .error {
  font-size: 0.85rem;
  color: #a5281b;
  margin-top: 0.35rem;
  font-weight: 700;
}

.field-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0 1.1rem;
}

/* The honeypot. Taken out of the flow entirely rather than `display: none` -
   some form fillers skip anything with `display:none` and fill everything else,
   and this one is meant to be filled. It carries `aria-hidden` and
   `tabindex="-1"` in the widget, so nothing a person uses can reach it. */
.hp-field {
  position: absolute !important;
  left: -9999px !important;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* The "I am not a robot" tickbox, and the line under it where the browser
   reports on the proof it is quietly solving. */
.field-check { margin-top: 1.4rem; }
.check {
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  cursor: pointer;
  font-weight: 400;
  font-size: 1rem;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink);
  margin: 0;
}
.check input[type="checkbox"] {
  /* 20px, and not shrunk by the flex row. A tick target smaller than this is
     one people miss on a phone. */
  width: 20px;
  height: 20px;
  flex: none;
  margin-top: 0.15em;
  accent-color: var(--blue);
  cursor: pointer;
}

.pow-status {
  margin: 0.45rem 0 0 1.9rem;
  font-size: 0.82rem;
  color: var(--ink-soft);
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
/* A spinning ring while the worker searches, a tick when it lands. Drawn in CSS
   rather than fetched, because it appears for well under a second. */
.pow-status[hidden] { display: none !important; }
.pow-status::before {
  content: "";
  width: 12px;
  height: 12px;
  flex: none;
  border-radius: 50%;
  border: 2px solid var(--sky-soft);
  border-top-color: var(--blue);
  animation: pow-spin 0.7s linear infinite;
}
.pow-status[data-state="done"] {
  color: #17603a;
  font-weight: 700;
}
.pow-status[data-state="done"]::before {
  border: 0;
  border-radius: 0;
  animation: none;
  width: 14px;
  height: 14px;
  /* A tick, as two sides of a rotated box. */
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translate(-1px, -2px);
  box-sizing: border-box;
  width: 7px;
  height: 12px;
}
@keyframes pow-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .pow-status[hidden] { display: none !important; }
.pow-status::before { animation: none; }
}

/* Whatever the form said back - a confirmation, or the one refusal a real
   visitor can trip. */
.notice {
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  padding: 0.9rem 1.1rem;
  border-radius: var(--radius);
  border: 1px solid;
  margin-bottom: 1.4rem;
  font-size: 0.95rem;
}
.notice .icon { margin-top: 0.2em; }
/* The way out, under whatever the form refused. Its own line, quieter than the
   refusal above it, and never the thing a visitor has to hunt for. */
.error-fallback { display: block; margin-top: 0.5rem; opacity: 0.9; }
.notice[tabindex]:focus { outline: 3px solid var(--sky); outline-offset: 3px; }

.notice-success { background: #eefaf2; border-color: #b7e4c9; color: #17603a; }
.notice-error { background: #fdeeec; border-color: #f2c1ba; color: #a5281b; }
.notice-info { background: var(--sky-wash); border-color: var(--sky-soft); color: var(--navy); }
.notice-warn { background: #fdf6e8; border-color: #ecd3a0; color: #7a4c05; }

/* ---------------------------------------------------------------------------
   12. Long-form documents - the imprint and the privacy notice
   ------------------------------------------------------------------------ */
.doc { max-width: 800px; }
.doc h2 {
  font-size: 1.35rem;
  margin-top: 2.6rem;
  padding-top: 1.6rem;
  border-top: 1px solid var(--line);
}
.doc h2:first-of-type { border-top: 0; padding-top: 0; margin-top: 1.5rem; }
.doc h3 { font-size: 1.05rem; margin-top: 1.8rem; color: var(--blue); }
.doc p, .doc li { color: #33455a; }
.doc ul { padding-left: 1.1rem; }
.doc li { margin-bottom: 0.35rem; }
.doc a { text-decoration: underline; text-underline-offset: 2px; }
/* URLs in a legal document are long and unbreakable, and one of them is what
   makes a phone scroll sideways. */
.doc a { overflow-wrap: anywhere; }

.doc-meta {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.85rem;
  color: var(--ink-soft);
  margin-bottom: 2rem;
}

/* The address block on the imprint. A definition list, because that is what it
   is - a set of labelled facts. */
.facts { display: grid; gap: 0; margin: 1.5rem 0; }
.facts > div {
  display: grid;
  grid-template-columns: minmax(150px, 200px) 1fr;
  gap: 1rem;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--line);
}
.facts dt { font-weight: 800; color: var(--navy); font-size: 0.88rem; }
.facts dd { margin: 0; }
@media (max-width: 560px) {
  .facts > div { grid-template-columns: 1fr; gap: 0.2rem; }
}

/* ---------------------------------------------------------------------------
   12b. The cookie notice
   ---------------------------------------------------------------------------

   Fixed to the foot of the window, and deliberately not a modal: it does not
   cover the page, it does not trap focus, and it can be ignored. A visitor who
   came to read the opening hours should be able to read them.
*/
.cookie-notice {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 120;
  background: #fff;
  border-top: 2px solid var(--blue);
  box-shadow: 0 -6px 24px rgba(17, 55, 99, 0.16);
  padding-block: 1rem;
  /* Below the iOS home indicator and any other bottom inset. */
  padding-bottom: max(1rem, env(safe-area-inset-bottom));
  animation: cookie-in 0.35s var(--ease) both;
}
@keyframes cookie-in {
  from { transform: translateY(100%); }
  to { transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .cookie-notice { animation: none; }
}

.cookie-inner {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 3vw, 2.5rem);
  flex-wrap: wrap;
}
.cookie-text { flex: 1 1 320px; }
.cookie-text strong {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--navy);
  font-size: 0.95rem;
}
.cookie-text strong .icon { color: var(--blue); }
.cookie-text p {
  margin: 0.3rem 0 0;
  font-size: 0.88rem;
  color: var(--ink-soft);
  max-width: 70ch;
}

.cookie-actions {
  display: flex;
  gap: 0.6rem;
  flex: 0 0 auto;
  margin: 0;
}
/* Both buttons the same size, and the reject one is not a grey link in the
   corner. A choice that is harder to decline than to accept is not a choice. */
.btn-sm { padding: 0.6rem 1.2rem; font-size: 0.88rem; }

@media (max-width: 620px) {
  .cookie-actions { width: 100%; }
  .cookie-actions .btn { flex: 1; }
}

/* ---------------------------------------------------------------------------
   13. The footer
   ------------------------------------------------------------------------ */
.site-footer {
  background: var(--footer);
  color: #b9c2cc;
  padding-block: clamp(2.5rem, 5vw, 4rem) 1.5rem;
  font-size: 0.92rem;
}
.footer-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: clamp(1.6rem, 4vw, 3rem);
  padding-bottom: 2rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
}
.site-footer h2 {
  font-size: 0.78rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #fff;
  margin-bottom: 1rem;
}
.site-footer a { color: #b9c2cc; }
.site-footer a:hover { color: #fff; text-decoration: underline; }
.site-footer ul { list-style: none; padding: 0; margin: 0; }
.site-footer li { padding: 0.24rem 0; }
.site-footer .footer-brand img { height: 36px; width: auto; margin-bottom: 0.9rem; }
.footer-contact li { display: flex; align-items: flex-start; gap: 0.55rem; }
.footer-contact .icon { color: var(--sky); margin-top: 0.28em; }
.footer-legal {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 1.6rem;
  justify-content: space-between;
  padding-top: 1.4rem;
  font-size: 0.84rem;
  color: #8b96a2;
}

/* ---------------------------------------------------------------------------
   13b. Utilities
   ---------------------------------------------------------------------------

   A short, closed list. Not a utility framework - just the handful of one-line
   adjustments the five pages actually repeat, so that a template can say what a
   thing *is* instead of carrying a style attribute. Anything used once and only
   once is still a rule of its own above, not a utility.
*/
.text-centre { text-align: center; }
.plain-list { list-style: none; padding: 0; margin: 0; }

.stack-sm { margin-top: 1.2rem; }
.stack-md { margin-top: 2rem; }
.stack-lg { margin-top: 2.5rem; }
.stack-under { margin-bottom: 2.5rem; }

/* The site's second voice: a light, roomy heading in the accent blue, used for
   the lines the original shouted rather than stated. Two sizes, because the
   front page's version is the loudest thing on it and the business page's is
   answering a question. */
/*
 * The question the original shouted, and the site's largest heading after the
 * hero's own h1.
 *
 * It was set at 1.3-2rem, which put it under the section headings around it -
 * a rhetorical question in the middle of the page, drawn smaller than the plain
 * headings either side, reads as a caption rather than as the turn in the
 * argument that it is. It is now 1.9-3.1rem, above every h2 on the site and
 * below the h1, which is where it belongs both visually and in the outline.
 */
.h-call {
  font-weight: 300;
  color: var(--sky);
  font-size: clamp(1.9rem, 4.6vw, 3.1rem);
  line-height: 1.16;
  letter-spacing: -0.005em;
  text-wrap: balance;
}
/* The business page asks its question in a narrower column, so it is set a
   step down from the front page's. */
.h-call-sm { font-size: clamp(1.5rem, 3.4vw, 2.3rem); }
.h-light { font-weight: 300; }

/* The answering line under a `.h-call`. */
.lede-call {
  color: var(--blue);
  font-size: clamp(1.1rem, 2.4vw, 1.6rem);
  font-weight: 300;
}
.lede-strong { color: var(--blue); font-weight: 700; }

/* A section heading in the accent blue - the two benefit lists that sit beside
   the plan of a car park, which the original set this way. */
.h-accent { color: var(--sky); font-weight: 700; }

/* The conclusion under a list: a sentence with the weight of a heading and the
   size of body text. */
.conclusion {
  margin-top: 1.2rem;
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--navy);
}

/* A labelled contact line - an icon, then the value. */
.contact-line { display: flex; gap: 0.55rem; padding: 0.3rem 0; }

/* A cut-out figure rather than a photograph in a frame: no shadow, no rounding,
   and capped by height because what matters is that the person stands the right
   size next to the text, not that the image fills its column.

   Nothing uses this at the moment - the one cut-out on the site was replaced by
   a photograph with a background of its own, which is `.portrait-tall` below.
   It is kept because the next cut-out will want it and because the rule is four
   lines. */
.cutout {
  margin-inline: auto;
  box-shadow: none;
  border-radius: 0;
  max-height: 520px;
  width: auto;
}

/* A standing portrait beside a column of text: capped by height for the same
   reason the cut-out is, but rounded and lifted, because it is a photograph
   with edges rather than a figure floating on the band. */
.portrait-tall {
  margin-inline: auto;
  max-height: 520px;
  width: auto;
  border-radius: var(--radius-lg);
  box-shadow: var(--lift-3);
  border: 3px solid rgba(255, 255, 255, 0.9);
}

/* A photograph that runs the full width of the content column. For a picture
   much wider than it is tall, which is the shape of a parking deck seen down
   its length - a column half this wide turns it into a strip. */
.panorama {
  margin: 0;
}
.panorama img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: var(--radius-lg);
  box-shadow: var(--lift-2);
}

/*
 * The three sign plates on the services page, standing in a row.
 *
 * A grid rather than a wrapping flex row, and this is worth the extra two lines.
 * Laid out as flex with a fixed plate width they fitted the column at some
 * widths and wrapped two-and-one at others - and two above one reads as a
 * mistake rather than as a set of three. The column here is half the shell,
 * which at 1440 is 512px and not the 552 the arithmetic suggests: the shell
 * spends two gutters of its own before the split divides what is left.
 *
 * `auto-fit` with a 118px floor keeps all three across from a phone's landscape
 * width upwards and drops to two only when three would be too small to read the
 * sign faces - which is the point at which wrapping is the right answer rather
 * than a fault.
 *
 * `align-items: end` lines their feet up. The plates are photographed at
 * slightly different heights, and hung from their tops they look like they are
 * falling over.
 */
.sign-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));
  align-items: end;
  justify-items: center;
  gap: clamp(0.7rem, 1.6vw, 1.2rem);
}
.sign-plate {
  margin: 0;
  text-align: center;
  max-width: 156px;
}
.sign-plate img {
  width: 100%;
  max-width: 150px;
  height: auto;
  display: block;
  margin-inline: auto;
  filter: drop-shadow(0 8px 16px rgba(17, 55, 99, 0.22));
  transition: transform 0.3s var(--ease);
}
.sign-plate:hover img { transform: translateY(-5px); }
.sign-plate figcaption {
  margin-top: 0.7rem;
  font-size: 0.83rem;
  line-height: 1.45;
  color: var(--ink-soft);
}


.mark-centred { margin: 0 auto 1.2rem; }

/* Sector tiles that are a heading and an icon with no list under them - the
   business page's four, which are centred where the front page's four are not. */
.sector-centred { text-align: center; align-items: center; }

/* A quote on a photographic band takes the band's colours. Here rather than in
   the template, so a quote moved onto a band cannot be left dark on dark. */
.band-photo .quote { border-color: #fff; color: #fff; }
.band-photo .quote footer { color: var(--sky-soft); }

.messages { padding-top: 1.5rem; }
.form-note { margin: 0.4rem 0 1.2rem; }

/* ---------------------------------------------------------------------------
   14. Motion
   ---------------------------------------------------------------------------

   Sections fade up as they come into view. The original animated most of its
   sections in the same way; this is the same idea done in a way that cannot
   leave content invisible.

   The rule that matters: the hidden state is applied by `reveal.js`, not by this
   stylesheet. If the script does not run - it failed, it was blocked, the
   browser is old - nothing ever adds `.reveal` and every section is simply
   visible. A stylesheet that hides content and waits for JavaScript to show it
   is how a page ends up blank.
*/
.reveal {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
  will-change: opacity, transform;
}
.reveal.is-in { opacity: 1; transform: none; }

/* Children of a revealed row come in one after another, so a row of four cards
   arrives as a row rather than as four things at once. */
.reveal-stagger > * {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
}
.reveal-stagger.is-in > * { opacity: 1; transform: none; }
.reveal-stagger.is-in > :nth-child(2) { transition-delay: 0.09s; }
.reveal-stagger.is-in > :nth-child(3) { transition-delay: 0.18s; }
.reveal-stagger.is-in > :nth-child(4) { transition-delay: 0.27s; }
.reveal-stagger.is-in > :nth-child(5) { transition-delay: 0.36s; }
.reveal-stagger.is-in > :nth-child(6) { transition-delay: 0.45s; }

/* A counter that runs up as it comes into view - the "24/365" and the "100%".
   The number is in the markup; the script only replaces it while it counts, so
   the real figure is what a crawler and a reader with no JavaScript see. */
.counter { font-variant-numeric: tabular-nums; }

/* Everything above, off, for anybody who has asked their system for less
   movement. Not "less movement" - none: the hero drift, the reveals, the
   smooth scrolling and the hover transforms all go, and the reveal states are
   forced visible in case the script has already hidden them. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .reveal, .reveal-stagger > * { opacity: 1 !important; transform: none !important; }
}

/* ---------------------------------------------------------------------------
   15. Print
   ---------------------------------------------------------------------------
   The two documents are the pages anybody prints - an imprint is printed to be
   filed, and a privacy notice to be read on paper. So the furniture goes and the
   text is set in black.
*/
@media print {
  .site-header, .site-footer, .btn, .band-photo::before, .band-photo::after { display: none !important; }
  body { color: #000; font-size: 11pt; }
  .band { padding-block: 0; }
  a { color: #000; text-decoration: underline; }
  /* A link on paper that does not say where it goes is a dead end. */
  .doc a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; }
}
