/*
 * No webfonts, no framework, no icon library. Everything here loads with the HTML.
 * The playbook lesson is blunt: on airport 3G a slow page is a lost booking, so the
 * whole stylesheet is small enough to inline and there is nothing external to fetch.
 */

/*
 * Palette "baie des anges". Sampled from the logo, then narrowed to one navy and one teal.
 *
 * The teal #017896 is a real value out of picks/logo-master.png — the third most frequent
 * colour in the mark that is not shading — which is why the page and the logo read as one
 * thing. The navy was pulled to #0b2340, the value the headings already used, so every
 * dark surface and every heading are one colour rather than two navies a shade apart.
 *
 *   --navy    #0b2340   header ground, hero, footer, closing band, step numbers
 *   --ink     #0b2340   headings — the same value, named for its job
 *   --ink-2   #4d6076   body text. 6.5:1 on white
 *   --accent  #017896   the logo teal. 5.09:1 on white, so it can carry link text as well
 *                       as buttons, which a brighter cyan could not
 *   --accent-hover   #00677f   6.47:1 with white on it
 *   --accent-pressed #00566b   8.26:1 with white on it
 *   --line    #d8e1ea   card and divider rule. Decorative only — never the sole marker of
 *                       an interactive control, which is what --field-line is for
 *   --bg      #ffffff   the reading ground
 *   --bg-2    #f4f8fb   the tinted band. Used where a section changes register, not on
 *                       every other section — see the note over section.alt below
 *   --bg-3    #eaf6f8   highlighted or selected information — callouts, the selected
 *                       segment's track, pills
 *   --wa      #25d366   WhatsApp, and nothing else on the site
 *
 * The accent is deliberately not red: the WhatsApp button is the other loud element on
 * every page, and two shouting colours side by side make the choice slower, not faster.
 * Every pair above clears WCAG AA (4.5:1) in both modes.
 *
 * Three colours were retired, each a consolidation rather than a loss. --cyan #00919f drew
 * one thing, the event-card rule, one step off the accent; it now draws it in the accent.
 * --ok #157a45 carried "return saves 5%" and the guide verdict panel — a green that is not
 * WhatsApp's green, two centimetres from the WhatsApp button inside the booking card, was
 * the one place where colour said something the words did not mean. --gold-deco tinted
 * .pill.gold, a rule nothing rendered. --gold survives for one job, the review stars,
 * because a gold star reads as a star and a teal one does not.
 *
 * --field-line #7b93a8 is 3.2:1 on white and marks anything a visitor can operate whose
 * only boundary is a line: form fields, the currency pills, the menu button, the ghost
 * button. --line is 1.32:1 and draws every non-interactive divider, which 1.4.11 allows.
 */

:root {
  --navy: #0b2340;
  --ink: #0b2340;
  --ink-2: #4d6076;
  --line: #d8e1ea;
  --bg: #ffffff;
  --bg-2: #f4f8fb;
  --bg-3: #eaf6f8;
  --accent: #017896;
  --accent-hover: #00677f;
  --accent-pressed: #00566b;
  --accent-ink: #ffffff;
  --wa: #25d366;
  --gold: #7d600f;
  --field-line: #7b93a8;
  --radius: 14px;
  --shadow-sm: 0 1px 3px rgba(10, 36, 52, .08);
  --shadow: 0 6px 24px rgba(10, 36, 52, .10);
  --shadow-lg: 0 18px 50px rgba(10, 36, 52, .16);
  --maxw: 1180px;

  /* One vertical rhythm for the whole page. Every band — the hero included — is padded
     from this, so the top of the page cannot drift out of proportion with what follows
     it. Fluid rather than fixed: a 56px gap that reads as generous at 1440 reads as
     wasted screen at 380, and as mean at 1920. */
  --space-section: clamp(38px, 4.4vw, 60px);
  /* The booking card's column: a floor, a width that tracks the viewport, and a ceiling
     past which extra width would only stretch the selects.
     40vw rather than something more modest, because width is how this card gets shorter.
     Its rows and its buttons go two-up on their own once the card is wide enough (see the
     container queries on the widget), and the difference between a 330px card and a 400px
     one is about 170px of height — which the hero, and on the stacked layouts the whole
     top of the page, pays for. The ceiling is where the two-up arrangement has all the
     room it needs and more would just be air. It was 430 until the Geneva layout put the
     country code and the phone number side by side next to the email, where 430 cut the
     number field's own placeholder off at "Phone num"; 470 shows it whole, and the hero
     headline still breaks in two lines at 1440. */
  --quote-col: clamp(330px, 40vw, 470px);
}

@media (prefers-color-scheme: dark) {
  :root {
    --navy: #041b2e;
    --ink: #e6edf4; --ink-2: #9fb2c4;
    --line: #1e3448; --bg: #081420; --bg-2: #0d1e2c; --bg-3: #122636;
    --accent: #35b5cf; --accent-ink: #04222c;
    --accent-hover: #57c4da; --accent-pressed: #7ad3e6;
    --gold: #e0bd57;
    --field-line: #4a6b8a;
    --shadow-sm: 0 1px 3px rgba(0,0,0,.5);
    --shadow: 0 6px 24px rgba(0,0,0,.45);
    --shadow-lg: 0 18px 50px rgba(0,0,0,.55);

  }
}

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

/* The padding is the sticky header's 64px plus 16 of air. Without it every fragment on the
   site lands 64px too high and the header sits on top of what was jumped to: #quote put
   "Get a Transfer Quote" and its subline under the bar, so the card a visitor had just
   asked for opened on the Pickup field with its own heading hidden — which reads less like
   an anchor than like a page that loaded wrong. Same for #airports and the skip link's
   #main. scroll-padding rather than scroll-margin on each target, because the offset is a
   property of the viewport's fixed furniture, not of any one section. */
html { scroll-behavior: smooth; scroll-padding-top: 80px; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 16px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
}

h1, h2, h3, h4 { line-height: 1.18; margin: 0 0 .5em; letter-spacing: -.015em; font-weight: 700; }
h1 { font-size: clamp(30px, 5vw, 48px); }
/* The 30px floor is too big for one word at 320px: "Geschäftsbedingungen" and
   "конфиденциальности" are each wider than the 280px of text column on the smallest
   phones, and a single word cannot wrap, so the legal pages scrolled sideways. 26px fits
   every heading word in the five languages; break-word is the backstop for one that ever
   does not, and only breaks a word that cannot fit a line on its own. */
h1, h2 { overflow-wrap: break-word; }
@media (max-width: 340px) { h1 { font-size: 26px; } }
h2 { font-size: clamp(23px, 3.2vw, 32px); }
h3 { font-size: 19px; }
p { margin: 0 0 1em; }
a { color: inherit; }
img { max-width: 100%; height: auto; display: block; }
ul { margin: 0 0 1em; padding-left: 1.15em; }
li { margin-bottom: .4em; }

.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 20px; }
.narrow { max-width: 760px; }
.center { text-align: center; }
.muted { color: var(--ink-2); }
.small { font-size: 14px; }
.tiny { font-size: 12.5px; }
section { padding: var(--space-section) 0; }
section.tight { padding: calc(var(--space-section) * .62) 0; }
/* The tinted band, and not an every-other-section stripe: mechanical alternation put half
   the page on pale blue, which is not a rhythm. The tint marks a change of register — the
   trust band, the shortcut route lists, the fleet, the meeting-point promise, the price
   table, the FAQ — leaving runs of one or two white sections between tinted ones. Which
   sections carry it is decided per page in src/templates/pages.js. */
section.alt { background: var(--bg-2); }
.lede { font-size: clamp(17px, 2vw, 20px); color: var(--ink-2); max-width: 64ch; }

/* ---------------------------------------------------------------- header */
.site-header {
  position: sticky; top: 0; z-index: 50;
  /* Opaque, not a 92% wash behind a 10px backdrop blur: a translucent header takes
     whatever scrolls under it into the contrast of its own nav labels, and a
     backdrop-filter on a sticky element reblurs on every scroll frame. */
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}
.site-header .wrap { display: flex; align-items: center; gap: 18px; height: 64px; }
.logo { display: flex; align-items: center; gap: 9px; font-weight: 800; text-decoration: none; letter-spacing: -.02em; font-size: 17px; white-space: nowrap; }
/* Height-driven, width auto: the emblem keeps its own proportions whatever crop it is
   replaced with, and the intrinsic width/height on the tag still reserves the space. */
.logo img { height: 34px; width: auto; flex: none; }
.site-footer .logo img { height: 38px; }
.site-nav { display: flex; gap: 4px; margin-left: auto; align-items: center; }
.site-nav a { text-decoration: none; padding: 8px 11px; border-radius: 9px; font-size: 14.5px; color: var(--ink-2); font-weight: 500; }
.site-nav a:hover { background: var(--bg-2); color: var(--ink); }
/* The page you are on. Colour plus an underline plus weight, because 1.4.1 does not let
   the accent be the only thing saying it. */
.site-nav a[aria-current="page"] { color: var(--accent); font-weight: 650; text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 6px; }
/* .site-nav a outranks .btn-primary on specificity, so the header CTA needs its own rules
   restated or it renders muted grey text on the accent fill. Hover and press are restated
   too — pinning both states to the base accent, which is what the single combined rule
   here used to do, left the one button in the header as the only primary on the site that
   did not respond to the pointer. */
.site-nav a.btn-primary { background: var(--accent); color: var(--accent-ink); }
.site-nav a.btn-primary:hover { background: var(--accent-hover); color: var(--accent-ink); }
.site-nav a.btn-primary:active { background: var(--accent-pressed); color: var(--accent-ink); }
.lang-switch { display: flex; gap: 2px; margin-left: 6px; padding-left: 10px; border-left: 1px solid var(--line); }
.lang-switch a { padding: 6px 7px; font-size: 12.5px; text-transform: uppercase; letter-spacing: .04em; font-weight: 600; }
.lang-switch a[aria-current] { color: var(--accent); }
/* The header phone number. Sits between the language switch and the CTA, in the navy
   rather than the accent, because it is the second-choice action on desktop and must not
   compete with "Get your price". Below 769px it is removed: the sticky mobile bar carries
   a full-width call button, and two call links on one screen is one too many. */
.hdr-tel { display: flex; align-items: center; gap: 6px; margin-left: 4px; white-space: nowrap; font-variant-numeric: tabular-nums; }
.site-nav a.hdr-tel { color: var(--ink); font-weight: 650; font-size: 14px; }
.hdr-tel svg { width: 15px; height: 15px; flex: none; color: var(--accent); }
/* The number drops to its icon before the row runs out of space. The band has to reach
   1280px because the row is at its widest just above it — with the number spelled out the
   Russian header needs 1189px and the French 1176px, so hiding the text at 1120, as this
   did until 2026-09-23, left both overflowing between 1121 and their own width. 1280 rather
   than the 1240 that just fits: at 1240 the Russian row cleared the edge by 15px, which is
   inside the margin a different font stack can move it. */
@media (max-width: 1280px) and (min-width: 1101px) { .hdr-tel span { display: none; } }
@media (max-width: 768px) { .hdr-tel { display: none; } }

.nav-toggle { display: none; }

/* 1100px and not the 940 it was until 2026-09-23. The row is nine items and its width is
   whatever the language makes it: measured with the number already reduced to its icon, the
   English nav needs 989px and the Russian 1071px, so between 941 and 1070 the Russian,
   French and German headers overflowed the viewport and pushed the quote button off the
   right edge — a horizontal scrollbar on a laptop, on 3 of the 5 languages. Reducing the gap
   and the padding was tried and measured: it recovers 80px where Russian needed 134, so the
   labels do not fit at any honest size and the menu is the right answer. One breakpoint for
   every language rather than five, because it is the widest that has to fit, and 1024 —
   where English and Italian did still fit — is a tablet in landscape, where a menu button is
   what a visitor expects anyway. */
@media (max-width: 1100px) {
  .site-nav { display: none; position: absolute; top: 64px; left: 0; right: 0; flex-direction: column; align-items: stretch; background: var(--bg); border-bottom: 1px solid var(--line); padding: 8px 14px 14px; box-shadow: var(--shadow); }
  .site-nav.open { display: flex; }
  .site-nav a { padding: 12px 10px; font-size: 16px; }
  .lang-switch { margin: 6px 0 0; padding: 10px 0 0; border-left: none; border-top: 1px solid var(--line); }
  .nav-toggle { display: block; margin-left: auto; background: none; border: 1px solid var(--field-line); border-radius: 9px; padding: 8px 11px; color: var(--ink); font: inherit; font-size: 14px; cursor: pointer; }
}

/* ---------------------------------------------------------------- buttons */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 13px 22px; border-radius: 11px; border: 1px solid transparent;
  font: inherit; font-weight: 650; font-size: 15.5px; text-decoration: none; cursor: pointer;
  transition: transform .06s ease, background-color .15s ease, border-color .15s ease, color .15s ease;
}
.btn:active { transform: translateY(1px); }
/* Hover and press are darker teals, not brightness(1.08) — that lightened the fill, which
   is the wrong direction, and moved the label's contrast to a number nobody could state.
   These are stated: 6.47:1 and 8.26:1 with white on them. */
.btn-primary { background: var(--accent); color: var(--accent-ink); }
.btn-primary:hover { background: var(--accent-hover); }
.btn-primary:active { background: var(--accent-pressed); }
.btn-dark { background: var(--navy); color: #fff; }
.btn-dark:hover { background: color-mix(in srgb, var(--navy) 88%, #ffffff); }
.btn-ghost { background: transparent; color: var(--ink); border-color: var(--field-line); }
.btn-ghost:hover { background: var(--bg-2); border-color: var(--accent); color: var(--accent); }
.btn-wa { background: var(--wa); color: #05331b; }
.btn-wa:hover { background: color-mix(in srgb, var(--wa) 88%, #000000); }
.btn-lg { padding: 13px 20px; font-size: 16px; line-height: 1.4; width: 100%; }
.btn svg { width: 18px; height: 18px; }

/* ---------------------------------------------------------------- hero */
/* Padded from the same token as every section below it, so the hero is a band in the
   page's rhythm rather than a law unto itself. Its height is then whatever the copy and
   the booking card need — and since the card is the taller of the two, shortening the
   card (see the quote widget, further down) is what actually sizes this section. */
/* Navy. The three-stop gradient that used to be here ended on a teal-grey in neither the
   logo nor the palette, and the white radial glow under it was decoration over a photograph
   that already carries its own scrim. Both went, and the base stayed flat. */
/* --scrim is the cover overlay, and it is declared here — once, on .hero — because as of
   2026-09-26 every page wears the same cover layout and the same overlay over it. Both
   photographic heroes read this one property and nothing overrides it per page, which is
   what "consistent across every page" means in practice: one number, one place.

   .1 is the owner's setting: 90% transparent, 10% opacity. What it costs the copy is
   written out in full in the destination-hero block below. Read that before changing it —
   at this value the site is publishing a WCAG AA contrast failure on the heroes that carry
   text over a photograph, and that is a decision on record, not an oversight. */
.hero { --scrim: .1; background: var(--navy); color: #fff; padding: var(--space-section) 0; position: relative; overflow: hidden; }

/* A gradient again, but only where there is no photograph, and built so it cannot repeat
   either half of what got the last one deleted.

   NEITHER OBJECTION SURVIVES THE REWRITE. The teal-grey was an off-palette colour picked as
   an end stop; every stop here is var(--navy) itself, mixed with white or black, so the hue
   is the palette's by construction and it follows --navy into dark mode without a second
   rule. The glow was decoration laid over a photograph that already had a scrim doing the
   same job; this only paints heroes that have no photograph at all — :not(.hero-dest)
   excludes every hero with a cover photograph behind it and :not(.hero-shot) the home page.

   MEASURED RATHER THAN EYEBALLED, because that is what put .20 and .5 on this site before. White on
   the flat navy is 15.8:1. The lightest stop here — navy mixed 10% with white — is 11.8:1 in
   light mode and 13.2:1 in dark. AA wants 4.5 and AAA 7, so the copy has room at every point
   of the sweep, and the .82-alpha lede clears both as well.

   NO PAGE CARRIES IT ANY MORE, and that is why it stays. Since 2026-09-26 every hero on
   the site is a cover hero: the home page has its own photograph, the four hubs have their
   banners, a route page takes its destination photograph or else its departure airport's
   banner, and the four standalone heroes take the Nice banner. So this rule now matches
   only a hero that ended up with no photograph at all — a deleted banner, a config record
   that lost its `photo`, an assets directory that did not ship — and it is the floor under
   that case rather than a layout anybody chose. It is a wash, not a feature — at 155deg
   over a hero that is mostly copy and a booking card, what it buys is that the band stops
   reading as a printed slab. */
.hero:not(.hero-dest):not(.hero-shot) {
  background: linear-gradient(155deg,
    color-mix(in srgb, var(--navy) 90%, #fff) 0%,
    var(--navy) 48%,
    color-mix(in srgb, var(--navy) 88%, #000) 100%);
}
.hero .wrap { position: relative; z-index: 1; display: grid; grid-template-columns: 1fr var(--quote-col); gap: clamp(22px, 3vw, 44px); align-items: start; }
.hero h1 { color: #fff; margin-bottom: .35em; }
.hero .lede { color: rgba(255, 255, 255, .82); }
.hero-points { list-style: none; padding: 0; margin: 22px 0 0; display: grid; gap: 9px; }
.hero-points li { display: flex; gap: 10px; align-items: flex-start; color: rgba(255,255,255,.9); font-size: 15px; margin: 0; }
/* Pale rather than mint green: the tick shape already does that job and the site's only
   green is WhatsApp's. Literal, not var(--bg-3), because the hero is navy in both modes. */
.hero-points svg { width: 19px; height: 19px; flex: none; margin-top: 2px; color: #eaf6f8; }
.eyebrow { display: inline-block; font-size: 12.5px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: rgba(255,255,255,.62); margin-bottom: 14px; }

/* ---------------------------------------------------------------- hero photograph
   assets/hero/nice-airport-transfers-chauffeur-*, cut from picks/hero-master-clean.png.

   Where things are, in master pixels of the 1671x941 frame, because every number below is
   derived from these: terminal and canopy x 0-700, palm x 520-720, chauffeur x 250-470 with
   his head at y 300, V-Class x 190-700, family x 690-878 with heads at y 390 and feet at
   y 915, luggage x 600-900. Right of x 890 the frame is sky, coast and sea and nothing
   else — that is where the headline used to be printed, and it is now the clear ground the
   booking card sits on. The card being able to sit there is the whole reason this frame
   works with a widget on the right.

   The overlay over it is the site-wide cover overlay — flat, one value, the same on this
   page as on every other — and at --scrim .1 it is very nearly not there. It no longer
   shapes itself round the copy; see the cover overlay rule below. */
@media (min-width: 1200px) {
  /* A ratio, not a length. A full-bleed cover background crops vertically by however much
     shorter the box is than the photograph, so a hero fixed at 690px crops harder the wider
     the window gets. Pinning height to width holds the crop still: 47vw keeps roughly the
     top 880 rows of the master on screen at any width, and the feet are at row 915, so the
     family stands on the ground rather than being cut through the ankles.

     Two ceilings, because a hero can be too big in two ways. 88vh is the one that keeps the
     trust bar under the fold — and it is 88 rather than 84 because at 1920x1080 the 84vh
     cap cropped the last four rows of the band, which is the tips of the shoes and the
     kerb line: the one crop on this photograph that reads as a mistake rather than a
     framing. At 88vh the feet clear at every size checked from 1280 to 1920, and the trust
     bar still shows. 1120px is the hard stop for very wide monitors, where 50vw would ask
     for 1280. */
  .hero-shot { min-height: clamp(560px, min(50vw, 88vh), 1120px); padding-block: clamp(18px, 1.8vw, 34px); }
  .hero-shot .wrap { align-items: start; }
  /* The copy is anchored to the top because its budget is measured from there. The card has
     no such constraint — behind it is empty sky at any height — so it takes the slack. */
  .hero-shot .quote-card { align-self: center; }
}
/* Three layers, and the order matters: photo, scrim over it, copy over both. Without the
   explicit z-index the picture paints after the ::before meant to dim it, and the headline
   ends up white on bright sky. */
.hero-photo { position: absolute; inset: 0; z-index: 0; display: block; }
/* Anchored to the top, and that is what buys the copy its room. The heads are at y 390, so
   the clear band above the family is 390 x (viewport / 1671) tall — 336px at 1440, 448px at
   1920 — and any downward pan spends it. What it costs is the bottom of the frame, which is
   kerb and tarmac. Losing tarmac is cheaper than putting a headline over a face. */
.hero-photo img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 0%; }
/* ------------------------------------------------- the cover overlay
   One rule, both photographic heroes, one value, and that is the whole point of it. The
   home page and the destination pages used to knock their photographs back in two different
   ways — a shaped three-gradient scrim here, a flat wash there — so "the overlay" was two
   numbers on two geometries and could not be set once. It is one declaration now, reading
   --scrim from .hero, so every cover hero on the site is dimmed by the same amount.

   WHAT THE SHAPED VERSION DID, because the problems it was shaped around are still in the
   photograph. It laid three gradients over the home frame: an ellipse under the copy block
   at the top left, cut off on the vertical before it reached the family's heads at master
   y 390 and stopping short of them at x 690; a wash down the left edge sized to the white
   AÉROPORT NICE CÔTE D'AZUR lettering on the terminal glass at x 20-320, which the lede
   crosses at every desktop width; and a top-to-bottom wash so the sticky header had
   something to sit against and the trust bar's border did not land on bright tarmac. Those
   three things — the signage under the lede, the faces beside the copy, the header edge —
   have not moved. They are simply no longer treated, because a flat overlay has no shape to
   put anywhere and 10% of navy does not hide lettering.

   A flat overlay is also the only kind that can be set by one number without lying about
   what it does. A shaped scrim is strong where you look for it and thin at the far corner of
   the copy block, which is how this site shipped a contrast failure nobody caught; see the
   history in ACCESSIBILITY.md. Flat has no thin part. At --scrim .1 it has no thick part
   either. */
.hero-shot::before,
.hero-dest::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(rgba(0,26,50,var(--scrim)), rgba(0,26,50,var(--scrim)));
}
.hero-shot .wrap { z-index: 2; }
/* The copy sits on bright sky with a 10% overlay over it, so it carries its own shadow. It
   was written for the copy that ran past the edge of the shaped patch; with the patch gone
   it is all there is. Note what it is not: WCAG does not count text-shadow, so this makes
   the line legible on a good monitor without making it pass. The equivalent for the
   destination heroes is further down, a couple of hundredths heavier. */
.hero-shot h1 { text-shadow: 0 2px 16px rgba(0, 14, 26, .6), 0 1px 3px rgba(0, 14, 26, .5); }
.hero-shot .lede,
.hero-shot .hero-points li,
.hero-shot .eyebrow { text-shadow: 0 1px 12px rgba(0, 14, 26, .68), 0 1px 2px rgba(0, 14, 26, .55); }

/* The copy block has a hard ceiling here, and it is arithmetic rather than taste: the
   tallest head is at master y 390, the picture is drawn at viewport / 1671, so the clear
   band above the family is 390 x that scale — 336px at 1440, 280px at 1200 — and the hero's
   own top padding comes out of it first.

   Wide rather than narrow, because the constraint is height and width is what buys height
   back: at a 46ch measure the headline ran to three lines and the lede to four. */
@media (min-width: 1200px) {
  .hero-shot .hero-copy { max-width: min(100%, 640px); }
  .hero-shot h1 { font-size: clamp(27px, 2.2vw, 33px); margin-bottom: .3em; }
  .hero-shot .eyebrow { font-size: 12px; margin-bottom: 6px; }
  .hero-shot .lede { font-size: clamp(15.5px, 1.1vw, 16.5px); line-height: 1.45; max-width: none; margin-bottom: 0; }
  /* The two claims are the last 60px in the budget and the first that can go. Measured at
     1440x900: with them the copy block ends at y 326 and the heads are at y 311, so the
     lede lands across the mother and the father. Without them it ends at 266 and clears
     them by 45px. They stay on for the stacked layout below 1200, where the copy sits on
     the hero own navy under the picture and costs nobody a face. */
  .hero-shot .hero-points { display: none; }
}

/* Below 1200 the picture stops being a background and becomes a band across the top. It has
   to: stacked, the hero is over 1100px tall and the full frame leaves nothing to crop
   vertically, so any pan puts the booking card over somebody. The band is also a different
   file — the master cropped to x 0-1080, y 90-941 — because at 390px wide the full frame
   renders each face about seven pixels across and no object-position fixes that. The media
   query on the <source> switches at this same 1200. */
@media (max-width: 1199px) {
  .hero-shot { padding-top: 0; }
  .hero-shot .hero-photo { position: relative; inset: auto; width: 100%; aspect-ratio: 1080 / 851;
    max-height: min(58vh, 470px); overflow: hidden;
    -webkit-mask-image: linear-gradient(180deg, #000 78%, transparent 100%);
            mask-image: linear-gradient(180deg, #000 78%, transparent 100%); }
  /* 8% down the overflow rather than centred: it keeps the heads — at y 300 of the crop —
     inside the band at every width the cap applies to. */
  .hero-shot .hero-photo img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 8%; }
  /* Nothing is over the photograph now — the copy sits below it on the hero's own navy, and
     the mask has faded the picture out by the time the eyebrow rides up into it. */
  .hero-shot::before { content: none; }
  .hero-shot .wrap { margin-top: -26px; }
}

/* ------------------------------------------------- destination hero (cover hero)
   assets/destinations/<place>-640/960/1280.jpg, one photograph of where the car is going —
   and, since 2026-09-26, assets/banners/<IATA> behind every other hero on the site too. This
   is the cover layout: a full-bleed photograph, the flat overlay above, the copy over both.
   It carries the four hubs, all the route pages, and the four standalone heroes, and it
   shares .hero-photo and the overlay with the home page.

   What still separates it from the home hero is geometry, not treatment. The home frame is a
   photograph of people and its numbers are measurements of where their faces are: the copy is
   capped so the lede clears their heads, and the two hero points are dropped above 1200 to
   buy that clearance. A destination photograph has no faces and no protected region — it is a
   city, edge to edge, bright everywhere — so the hero points stay on at every width because
   nothing is behind them that they can spoil, and the frame is 16:9 against the home hero's
   2:1. The overlay itself is the same flat knock-back on both, at the same value. */
.hero-dest .wrap { z-index: 2; }
/* One number, and it is deliberately one number — and since 2026-09-26 it is also one
   number for the whole site rather than one for the destination heroes. The declaration
   moved up to .hero and the ::before moved up to the shared cover overlay rule above; what
   is left here is the arithmetic, because this is where it was worked out and this is the
   block anyone will come looking for.

   The first version was three gradients shaped round where the copy sits, which is how the
   home hero used to work — and it is why the first version shipped a contrast failure nobody
   caught: a shaped scrim is strong where you look for it and thin at the far corner of the
   block, and the four hero points end up in the thin part over a white hull. Flat has no
   thin part. It also means anyone can retune this without reading the paragraph above it:
   change --scrim, nothing else moves.

   The value is the owner's to set, and as of 2026-09-26 it is set at .1 — 90% transparency,
   10% opacity — on the owner's instruction, sitewide. What each setting costs against the
   4.5:1 white text is owed. The worst case is white text over a pixel of pure white knocked
   back by this navy:

       --scrim   .65    .62    .59    .587   .58    .50    .20    .10     0
       worst    5.56   5.02   4.54   4.50   4.40   3.42   1.54   1.23  1.00

   THE SETTING IS BELOW THE FLOOR AND THE FLOOR IS NOT A MATTER OF TASTE. .587 is where white
   copy over the brightest pixel a photograph can contain reaches 4.5:1. At .1 that pixel
   measures 1.23:1. The site shipped .20 — 1.54:1 — until 2026-08-29, when raising it to .59
   closed the last recorded AA failure on the site; .1 is lower than the value that failure
   was made of, and it is now on the home page and the four standalone heroes as well as on
   every route page and hub, because the cover layout is sitewide. This is a published AA
   failure by decision. It is recorded here and in ACCESSIBILITY.md rather than softened.

   .587 is the floor, not .58 — and the difference is why the row is written out at all. Both
   round to "about .59" in prose and only one of them passes: at .58 a white pixel under white
   text measures 4.40:1, a fail by a tenth. .59 was the house setting from 2026-08-29 to
   2026-09-26 because it is the first sane number above the floor, and because three records
   in site.config.json had already been given exactly that value for exactly this reason.

   Do not read the text-shadow below as slack in this number. It is why a failing scrim still
   looks acceptable at a glance on a good monitor, which is how .20 survived as long as it
   did. WCAG does not count text-shadow, and neither does a passenger reading a phone in
   sunlight in an arrivals hall.

   WHAT IS LEFT PROTECTING THE COPY, since the overlay is not. Below 1200px nothing at all
   sits on the photograph: the picture becomes a band across the top, the ::before is turned
   off, and the copy sits below it on the hero's own navy at 15.8:1. That is the whole phone
   and tablet range, and it is unaffected by --scrim at any value. The exposure is the
   desktop layout at 1200px and up, where the copy is composited over the frame. The honest
   fix for a frame that fights its copy has always been the same one: stop putting text on
   top of it, which is exactly what the layout already does below 1200. See ACCESSIBILITY.md.

   Per-photograph overrides are gone and should stay gone: `scrim` on a record in
   site.config.json still works and still emits an inline --scrim on that one page, which is
   the one thing that would break "the same overlay on every page". See the `_scrim` note
   there before adding one back. */
.hero-dest h1 { text-shadow: 0 2px 16px rgba(0, 14, 26, .62), 0 1px 3px rgba(0, 14, 26, .5); }
.hero-dest .lede,
.hero-dest .hero-points li,
.hero-dest .eyebrow { text-shadow: 0 1px 12px rgba(0, 14, 26, .7), 0 1px 2px rgba(0, 14, 26, .55); }

@media (min-width: 1200px) {
  /* A floor rather than a height. The booking card is still the tallest thing in this hero
     and still what sizes it, exactly as on the gradient route pages — this only stops a
     short-copy route from cropping the photograph to a letterbox. 36vw against the home
     hero's 50vw because there is less to see: the frame is 16:9 rather than 16:9.4, and
     the subject is spread across it rather than standing at one edge of it. */
  .hero-dest { min-height: clamp(420px, min(36vw, 72vh), 780px); padding-block: clamp(20px, 2vw, 36px); }
  .hero-dest .quote-card { align-self: center; }
  .hero-dest .hero-copy { max-width: min(100%, 620px); }
}
/* Centred rather than anchored to the top. Cover crops about 11% off a 16:9 frame in this
   box, and split top and bottom that is sky off the roof and water off the foot — where
   anchoring to the top would spend all of it on the waterfront, which on this photograph is
   the half of the picture worth showing. */
.hero-dest .hero-photo img { object-position: 50% 50%; }

/* Below 1200 the photograph becomes a band across the top, the same move the home hero
   makes and for a simpler reason: stacked, the copy and the card are over 900px tall and
   there is nothing left to crop the picture to. The band is the same files — a skyline is
   legible at 390px in a way a face is not, so unlike the home hero there is no second crop
   and no <source> switch, only this. */
@media (max-width: 1199px) {
  .hero-dest { padding-top: 0; }
  /* The fade starts at 70% and the copy rides up 14px, against the home hero's 78% and 26px,
     and the eight pixels are the whole difference between the two photographs. Both let the
     eyebrow sit inside the bottom of the picture so the band does not read as a separate
     block glued above the copy. The home hero fades into hazy sky, where whatever survives
     the mask is nearly the navy underneath it anyway; this one fades into lit buildings and
     water at around 180 luma. Measured at 375px with the earlier numbers, the eyebrow — 62%
     white, 12.5px, so normal text under WCAG rather than large — came out at 4.45:1 against
     what showed through, which is a fail. At 70% and 14px it is 6.2:1. */
  .hero-dest .hero-photo { position: relative; inset: auto; width: 100%; aspect-ratio: 16 / 9;
    max-height: min(38vh, 320px); overflow: hidden;
    -webkit-mask-image: linear-gradient(180deg, #000 70%, transparent 100%);
            mask-image: linear-gradient(180deg, #000 70%, transparent 100%); }
  .hero-dest .hero-photo img { width: 100%; height: 100%; object-fit: cover; }
  /* Nothing is over the photograph now — the copy sits below it on the hero's own navy. */
  .hero-dest::before { content: none; }
  .hero-dest .wrap { margin-top: -14px; }
}

@media (max-width: 900px) {
  .hero .wrap { grid-template-columns: 1fr; gap: clamp(20px, 3.4vw, 28px); }
}

/* ---------------------------------------------------------------- trust bar */
.trustbar { background: var(--bg-2); border-bottom: 1px solid var(--line); padding: 18px 0; }
.trustbar .grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
/* Stacked, not side by side: the picture is a band across the item now, with the two lines
   of text under it, which is the shape every card on the site carries. */
.trust-item { display: flex; flex-direction: column; gap: 10px; background: var(--bg); border: 1px solid var(--line); border-radius: 12px; padding: 13px 14px; }
.trust-item svg { width: 22px; height: 22px; flex: none; color: var(--accent); margin-top: 2px; }
/* The photograph, as a band across the item — the same 3:2 every card on the site carries,
   so the strip under the hero and the grids below it read as one family rather than two.
   It was a 56px circle until the cards became bands and left it the odd one out. */
.trust-photo { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; display: block;
  border-radius: 9px; background: var(--bg-3); }
.trust-item b { display: block; font-size: 14.5px; }
.trust-item span { display: block; font-size: 12.5px; color: var(--ink-2); margin-top: 2px; line-height: 1.4; }
@media (max-width: 860px) { .trustbar .grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 460px) {
  /* Already stacked at every width now; only the padding and the type tighten here. */
  .trust-item { gap: 8px; padding: 11px 10px; }
  .trust-item b { font-size: 13px; } .trust-item span { font-size: 11.5px; }
}

/* ---------------------------------------------------------------- quote widget */
/*
 * THE GENEVA LOOK, since 2026-09-19. The owner asked for this card to match the one on
 * genevaairporttransfer.net: dark glass over the hero, a green band carrying the heading,
 * blue capital labels, fields that turn white once they hold an answer, and the two
 * buttons full width one above the other. It is dark in both colour schemes, because
 * every hero it sits in is navy or a photograph in both.
 *
 * Three places where it deliberately parts from Geneva, all contrast:
 *   - The labels are #6fa8dc, not Geneva's steelblue #4682b4. Steelblue on that navy is
 *     4.0:1, and 2.0:1 where a bright photograph shows through; #6fa8dc is 6.9:1 on the
 *     card and still 4.9:1 over pure white behind the glass.
 *   - The glass is 88% opaque where Geneva's is 78%, which is what keeps that 4.9:1.
 *   - The WhatsApp button keeps this site's dark-green lettering. White on #25d366 is
 *     2.0:1, the one colour pair on Geneva's card that nobody can read in sunlight.
 * The band is Geneva's #1f7a4d, which carries white at 5.3:1. It is the second green on
 * the site after WhatsApp's, and it is here only because this is the WhatsApp form.
 *
 * `container-type` makes the card's width its own business rather than the viewport's.
 * The card appears in a hero column, in a stacked mobile layout and on the airport and
 * route pages, and the question its rows need answered is "how wide am I", not "how wide
 * is the window" — a media query gets that wrong the moment the card is not the full
 * width of the screen.
 */
.quote-card {
  container-type: inline-size;
  --q-label: #6fa8dc;
  color: #fff; border-radius: 16px; overflow: hidden;
  background: rgba(8, 26, 46, .88);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, .3);
  box-shadow: 0 26px 60px rgba(0, 0, 0, .34);
  color-scheme: dark;
}
.q-head { background: #1f7a4d; padding: clamp(12px, 1.1vw, 16px) clamp(14px, 1.3vw, 20px); }
.quote-card h2 { color: #fff; font-size: clamp(18px, 1.5vw, 22px); margin: 0 0 3px; }
.q-sub { font-size: 13.5px; line-height: 1.4; color: rgba(255, 255, 255, .92); margin: 0; }
.q-body { padding: clamp(10px, 1vw, 14px) clamp(14px, 1.3vw, 20px) clamp(14px, 1.3vw, 18px); }
.quote-card label { display: block; font-size: 11.5px; line-height: 1.3; font-weight: 800; color: var(--q-label); margin: 9px 0 4px; text-transform: uppercase; letter-spacing: .08em; }
.q-body > label:first-child { margin-top: 0; }
/* 16px on the inputs and not a decimal less: iOS Safari zooms the whole page in on focus
   for anything smaller, and a viewport that jumps mid-booking is worse than a card two
   pixels taller. The line-height is what gives the height back — the body's 1.6 is a
   reading measure and a select is not a paragraph. The min-height evens out what the
   browsers give a select, a text field and a date field at the same padding, so a row of
   two reads as one row. 44px and not the 42 it was until 2026-09-23, because that is the
   floor both Apple and WCAG 2.5.5 put under a touch target, and these are tapped with a
   thumb on a phone at the kerb. It is a floor, not a height: the padding and line-height
   still decide, and every control here is short enough that the floor is what answers.
   The border is 42% white, 3.5:1 on the glass at worst, because on an empty field it is
   the only thing that shows there is a field. */
.quote-card input, .quote-card select {
  width: 100%; min-width: 0; min-height: 44px; padding: 8.5px 11px; font: inherit; font-size: 16px; line-height: 1.3;
  color: #fff; background: rgba(255, 255, 255, .12); border: 1px solid rgba(255, 255, 255, .42); border-radius: 8px;
  text-overflow: ellipsis;
}
.quote-card input::placeholder { color: rgba(255, 255, 255, .68); opacity: 1; }
.quote-card select option, .quote-card select optgroup { color: #0b2340; background: #fff; }
/* Answered. quote.js sets the class, because a date, a time and a select have no
   :placeholder-shown to test. `color-scheme: light` is what turns the calendar and clock
   icons dark again on the white ground. */
.quote-card .is-filled { color: #0b2340; background: rgba(255, 255, 255, .94); border-color: rgba(255, 255, 255, .94); color-scheme: light; }
.quote-card input:focus-visible, .quote-card select:focus-visible { outline: none; border-color: var(--q-label); box-shadow: 0 0 0 3px rgba(111, 168, 220, .45); }
/* Asked for and not yet given. Set by quote.js when a send is blocked, and dropped again the
   moment the field is answered. The border carries it rather than a colour on the text,
   because an empty field has no text to colour, and it is paired with the named list in
   #q-status so it does not rely on colour alone. */
.quote-card .is-missing { border-color: #ffb4a8; box-shadow: 0 0 0 3px rgba(255, 180, 168, .28); }
.q-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 0 10px; }
/* The phone gets the wider half, since it holds two controls. */
.q-row-contact { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); }
/* The code and the number read as one field: one outline, one radius, split by a rule. */
.q-phone { display: flex; }
/* Both halves are <input> since 2026-10-14 — the code stopped being a <select> when it
   became a type-ahead — so these are keyed on the ids rather than the element names. */
.q-phone #q-code { flex: 0 0 min(40%, 150px); padding-inline: 9px; border-radius: 8px 0 0 8px; }
.q-phone #q-phone { flex: 1 1 auto; border-radius: 0 8px 8px 0; border-left-width: 0; }
@container (max-width: 400px) {
  .q-row-contact { grid-template-columns: minmax(0, 1fr); }
}
/* Google's address suggestions, drawn by quote.js under the place field being typed in.
   The wrapper takes no height, so the list floats over the fields below it instead of
   pushing them down. White, like an answered field, with the page's own navy and slate:
   #4d6076 is 6.5:1 on white. The credit is Google's own spec — "Google Maps" in a normal
   weight at 12px in #5e5e5e, inside the same box as the suggestions. */
.q-ac { position: relative; z-index: 5; }
.q-ac-pop { position: absolute; left: 0; right: 0; top: 4px; background: #fff; color: #0b2340;
  border-radius: 10px; box-shadow: 0 14px 34px rgba(0, 0, 0, .38); overflow: hidden; }
.q-ac ul { list-style: none; margin: 0; padding: 4px 0; }
/* A pin column and then the two lines of text beside it, rather than the text starting at
   the edge: the icons line up down the list and give the eye a rail to run along. */
.q-ac li { margin: 0; padding: 7px 12px; cursor: pointer; line-height: 1.35;
  display: flex; align-items: center; gap: 9px; }
.q-ac-pin { width: 16px; height: 16px; flex: none; color: #7d8ea3; }
/* One line, and it ellipses as one: the name and the address that qualifies it belong to
   the same sentence, so the cut falls at the end of the row rather than between them. */
.q-ac-line { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.q-ac li b { font-size: 14.5px; font-weight: 500; }
.q-ac-sub { font-size: 12.5px; color: #4d6076; margin-left: 6px; }
/* The typed letters carry the weight, not the whole name — the name is what the row says,
   the match is what the visitor is looking for. No background: a highlighter bar down a list
   of five is noise, and <mark>'s default yellow belongs to a search result page. */
.q-ac li mark { background: none; color: inherit; font-weight: 800; }
.q-ac li:hover, .q-ac li[aria-selected="true"] { background: #eaf6f8; }
.q-ac-credit { margin: 0; padding: 5px 12px 6px; border-top: 1px solid #e3e8ee; text-align: right; }
.q-ac-credit img { display: inline-block; width: 144px; height: 18px; vertical-align: middle; }
/* An open list owns the screen. Both of these are fixed and painted from the page root, so
   they cover the bottom of the list however high its own z-index goes — see floatersAside()
   in assets/quote.js, which sets this class. Nothing is lost by hiding them: a visitor part
   way through typing an address is not reaching for Call or WhatsApp, and both are back the
   moment the list closes. */
body.q-ac-open .mobile-bar, body.q-ac-open .wa-float { display: none; }
@container (max-width: 250px) {
  .q-row { grid-template-columns: minmax(0, 1fr); }
}

/* The segmented Return / One way control, the price panel and the currency switch stood
   here until 2026-08-30. They went with the widget: the hero now carries an enquiry form
   rather than a calculator, so there is no price on screen to size, no currency to switch
   and no return discount to toggle. Deleted rather than left dead — the stylesheet ships on
   every page, and rules for markup that no longer exists are how a file gets to be twice
   the size it needs to be. The maths and the copy are in the history if the price ever
   comes back; see the note where the parity check used to be in src/build.js. */

/* A pair of buttons, side by side when both labels fit on one line and stacked when they
   do not, with the buttons deciding which: `min-width: max-content` makes each refuse to
   be narrower than its own label, `flex-wrap` drops the second onto its own line, and
   `min(100%, …)` gives up and wraps a label wider than the whole row rather than push the
   page sideways. The closing band on most pages and the business page's contact pair use
   it as it is. */
.q-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.q-actions > * { flex: 1 1 auto; width: auto; min-width: min(100%, max-content); }
.q-actions .btn-lg { padding-inline: 12px; }
/* Inside the quote card, full width and one above the other, as on Geneva: the send
   button is the widest thing on the card and nothing competes with it. Capitals and a
   wide track for the same reason the labels have them. Scoped to the card, because the
   2026-09-19 version of these rules sat on .q-actions itself, set the business page's
   email address in capitals and pushed it 17px past a 360px screen. */
.quote-card .q-actions { display: grid; gap: 9px; margin-top: 14px; }
.quote-card .q-actions > * { width: 100%; min-width: 0; }
.quote-card .q-actions .btn { padding: 12px 14px; font-size: 15px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; border-radius: 9px; }
.quote-card .q-actions .btn svg { width: 18px; height: 18px; }
.btn-mail { background: transparent; color: #fff; border: 2px solid rgba(255, 255, 255, .85); }
.btn-mail:hover { background: rgba(255, 255, 255, .14); }
.q-note { margin: 10px 0 0; text-align: center; font-size: 12.5px; font-weight: 700; color: rgba(255, 255, 255, .85); }
.q-status { font-size: 13.5px; margin-top: 10px; }
/* It sits above the buttons now and is empty almost always, so it must take no room at all
   until it has something to say. */
.q-status:empty { margin: 0; }
/* A failed submit used to be teal text — the colour every link and button already uses to
   mean "this is fine, press it". It is now a panel: white text at weight inside a rule in
   the label blue, so the shape carries the state and no colour has to. */
.q-status.err { color: #fff; font-weight: 650; background: rgba(255, 255, 255, .1); border: 1px solid var(--q-label); border-radius: 10px; padding: 9px 12px; }
.q-status.err a { color: #fff; font-weight: 650; }


/* ---------------------------------------------------------------- cards / grids */
.grid-3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); gap: 18px; }
.grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); gap: 18px; }
.grid-4 { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(215px, 100%), 1fr)); gap: 16px; }

/* The fleet grid is not .grid-4, and the reason is arithmetic rather than taste. .grid-4
   floors at 215px, which inside --maxw fits five columns - fine for the four airports and
   the eight classes the fleet used to have, and wrong the moment it held six: five and a
   lone sixth, which reads as a card that failed to load rather than as a row.

   300px floors at three columns in 1140px of content, so six is 3 + 3. It falls to two
   columns at about 656px of viewport and one below that, and six divides evenly into both.
   Anything from 273 to 369 lands on three; 300 is the middle of that range, so a change to
   --maxw has to be large before this breaks.

   It also buys the photographs some size: a tile goes from about 173px wide to about 364,
   which is the difference between a thumbnail of a black car and a picture of one. */
.veh-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); gap: 16px; }

.card { background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius); padding: 20px; }
.card h3 { margin-bottom: .35em; }
/* A card heading is sized by the card, not by its level. The guides index carries its six
   titles as <h2> because they are the top level under that page's <h1> — there is no
   section between the two for an <h2> to name, so making them <h3> skipped a level to no
   purpose. They read at card size either way. */
.card h2 { font-size: 19px; margin-bottom: .35em; }
.card p:last-child { margin-bottom: 0; }

a.card-link { text-decoration: none; display: block; transition: border-color .15s, box-shadow .15s, transform .15s; }
a.card-link:hover { border-color: var(--accent); box-shadow: var(--shadow-sm); transform: translateY(-2px); }

/* ---------------------------------------------------------------- services / why us */
/* One tinted square per card, the icon inside it. The tint is mixed from the accent
   rather than hard-coded so the badge follows the theme into dark mode with everything
   else. */
.icon-badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: 11px; margin-bottom: 13px;
  background: color-mix(in srgb, var(--accent) 13%, transparent); color: var(--accent);
}
.icon-badge svg { width: 20px; height: 20px; }

/* The badge beside the words instead of above them.

   Two cards on this site are one wide row rather than a column: the four figures under a
   route heading (distance, journey time, price from, vehicles) and the guide teaser below
   them. On both, a 38px badge on its own line would add most of a line of height to a card
   whose entire content is a label and a number, and would say nothing the number does not.
   So the badge shrinks to 34px, loses its bottom margin and sits in the flex row.

   The selector is doubled because the teaser is an <a class="card-link">, and `a.card-link`
   sets `display: block` at a specificity a bare class cannot beat. */
.icon-badge.sm { width: 34px; height: 34px; border-radius: 10px; flex: none; }
.icon-badge.sm svg { width: 18px; height: 18px; }
.icon-row, a.card-link.icon-row { display: flex; gap: 13px; align-items: flex-start; }
.icon-row > .icon-badge { margin-bottom: 0; }

/* The four figures. Centred on the badge rather than top-aligned, because the text beside it
   is two short lines and hanging them from the top leaves the badge floating. The three
   declarations under it are the inline styles these tiles used to carry in the template. */
.stat-card { align-items: center; }
.stat-card h3 { margin: 0; }
.stat-num { font-size: 22px; font-weight: 700; margin: 2px 0 0; }

/* The photograph version of that badge: one band across the top of the card, at 3:2.
   Used wherever a card carries a picture — the six services, the six reasons, the eight
   event weeks. A photograph is worth the bytes when it shows the reader the actual place
   they are asking about; it is not worth them as decoration next to a heading that already
   says "free cancellation".

   3:2 because that is the ratio the masters were drawn at, and because it is one shape for
   the whole site. Where it comes from differs: the service masters are 3:2 photographs and
   the band is the whole frame, while the event and why masters are scenes inside a drawn
   navy ring and the band is the largest 3:2 rectangle that fits inside that ring — see
   prepare-images.ps1. Either way nothing is stretched, and the ring never shows.

   Fixed by aspect-ratio rather than by height, so the band holds its proportions as the
   grid goes from three columns to two to one and only the card's own width moves under it.
   The corner radius is the card's own minus its padding, so the picture looks cut from the
   card rather than dropped on it. */
.card-photo {
  display: block; width: 100%; aspect-ratio: 3 / 2; object-fit: cover;
  border-radius: calc(var(--radius) - 6px); margin-bottom: 15px;
  background: var(--bg-3);
}

.ticks { list-style: none; padding: 0; margin: 0; display: grid; gap: 9px; }
.ticks li { display: flex; gap: 10px; align-items: flex-start; margin: 0; font-size: 15px; }
.ticks svg { width: 18px; height: 18px; flex: none; margin-top: 3px; color: var(--accent); }

.meet-grid { display: grid; grid-template-columns: 1.15fr .85fr; gap: clamp(20px, 3vw, 40px); align-items: start; }
@media (max-width: 860px) { .meet-grid { grid-template-columns: 1fr; } }

/* ---------------------------------------------------------------- destination index */
/* Multi-column rather than a grid of equal cells: the four countries are wildly uneven —
   thirty-odd Swiss destinations against one German one — and in a grid that is three
   near-empty columns beside one very long one. Flowing the whole index instead lets
   Switzerland spill into the next column and the section come out roughly level. The
   heading is glued to the top of its own list; only the list is allowed to break. */
.dest-groups { columns: 4 230px; column-gap: 30px; margin-top: 24px; }
.dest-groups > div { margin-bottom: 20px; }
.dest-groups h3 { font-size: 13px; text-transform: uppercase; letter-spacing: .07em; color: var(--ink); border-bottom: 1px solid var(--line); padding-bottom: 9px; margin: 0 0 4px; break-after: avoid; }
.dest-list li { break-inside: avoid; }
.dest-list { list-style: none; padding: 0; margin: 0; }
.dest-list li { margin: 0; }
/* Teal, because every entry is a link to its own page and navy made them read as printed
   text. The journey time keeps --ink-2, so the row still splits into where and how long. */
.dest-list a { display: flex; gap: 12px; align-items: baseline; padding: 6px 0; text-decoration: none; font-size: 15px; color: var(--accent); }
.dest-list a:hover { color: var(--accent-hover); text-decoration: underline; }
.dest-list span { margin-left: auto; font-size: 12.5px; color: var(--ink-2); white-space: nowrap; }
/* One column on a phone, so the rows get the height a thumb needs. */
@media (max-width: 560px) { .dest-list a { padding: 8px 0; } }

/* ---------------------------------------------------------------- closing call to action */
.cta-band { background: var(--navy); color: #fff; }
.cta-band h2 { color: #fff; }
.cta-band .lede { color: rgba(255, 255, 255, .84); }

/* A <picture> that carries no class of its own is a wrapper and nothing else: it exists so
   the AVIF <source> has somewhere to sit, and it was added around images that had been
   direct children of a flex or grid container for years. `display: contents` takes it back
   out of the box tree, so .trust-item stays a four-item column, .veh-figure stays a grid
   with one centred child, and .logo stays a flex row — every selector written against those
   images keeps meaning what it meant. The heroes are excluded by having a class: .hero-photo
   is positioned and sized itself, and must stay in the tree to do it. */
picture:not([class]) { display: contents; }

/* ---------------------------------------------------------------- fleet pictures */
/* The drawings are files in /assets/vehicles/ used as plain images, so the browser
   caches them once for the whole site — see src/data/fleet-art.js. */
.veh-card { display: flex; flex-direction: column; }
/* One frame, one aspect, whatever is inside it. The fleet is part photographed and part
   drawn, and the two have different proportions — fixing the box and containing the
   picture keeps the row of cards level instead of ragged. */
/* The photographs are studio shots on white, so every tile is white in both modes —
   including the drawn classes, so a part-photographed fleet still reads as one set
   rather than as two. A toned tile would also show as a rectangle around each
   photograph's own white background. */
.veh-figure {
  aspect-ratio: 16 / 9; display: grid; place-items: center;
  background: #fff; border: 1px solid var(--line); border-radius: 10px;
  margin: -4px 0 14px; padding: 0; overflow: hidden;
}
.veh-figure img { width: 100%; height: 100%; object-fit: contain; display: block; }
/* A drawing is transparent artwork that should sit inside the tile with air around it, so
   `contain` is right for it. A photograph is not: contained, it letterboxes against the
   white tile — an 11% white band top and bottom, measured — and reads as a mistake rather
   than a picture. Photographs fill the tile and crop, which is also why the shooting spec
   in LAUNCH.md asks for 16:9 with the car centred. */
.veh-figure.has-photo img { object-fit: cover; }

/* ── The fleet lightbox ────────────────────────────────────────────────────────────────
   The tile is a link to the first photograph in its set, and fleet-gallery.js upgrades the
   click to the dialog below. Everything here is written so that the link on its own still
   looks and behaves like a link if the script never arrives. */
.veh-shots { display: block; position: relative; text-decoration: none; color: inherit; border-radius: 10px; }
.veh-shots:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }
/* The affordance. Bottom-right of the picture rather than centred over it, because the
   picture is the product and a label across the middle of it is a label across the car. */
.veh-more {
  position: absolute; right: 8px; bottom: 22px;
  background: rgba(8, 20, 38, .78); color: #fff;
  font-size: 11px; line-height: 1; padding: 5px 8px; border-radius: 999px;
  pointer-events: none; transition: background .15s ease;
}
.veh-shots:hover .veh-more, .veh-shots:focus-visible .veh-more { background: rgba(8, 20, 38, .92); }
.veh-shots:hover .veh-figure { border-color: var(--brand); }

.veh-lightbox { border: 0; padding: 0; background: transparent; max-width: 100vw; max-height: 100vh; }
.veh-lightbox::backdrop { background: rgba(6, 14, 28, .88); }
.vl-box { position: relative; display: grid; justify-items: center; gap: 6px; padding: 12px; }
/* The <picture> the photograph now sits in is a wrapper and nothing else — `contents`
   takes its box out of the grid so the <img> stays the grid item it has always been, and
   the rules below go on measuring the image rather than a container around it. */
.vl-box picture { display: contents; }
/* `contain`, and a box measured against the viewport rather than the image: the set holds
   upright interiors as well as landscape exteriors, and a lightbox that crops them to one
   ratio throws away the thing each was taken for. */
.vl-img {
  max-width: min(94vw, 1280px); max-height: 78vh;
  width: auto; height: auto; display: block; border-radius: 8px; background: #0b1524;
}
.vl-caption { color: #eaf1f8; font-size: 14px; margin: 4px 0 0; text-align: center; }
.vl-pos { color: #9fb3c8; font-size: 12px; margin: 0; }
.vl-prev, .vl-next, .vl-close {
  position: absolute; border: 0; cursor: pointer;
  background: rgba(10, 22, 42, .72); color: #fff;
  width: 44px; height: 44px; border-radius: 999px;
  font-size: 26px; line-height: 1; display: grid; place-items: center;
}
.vl-prev:hover, .vl-next:hover, .vl-close:hover { background: rgba(10, 22, 42, .95); }
.vl-prev:focus-visible, .vl-next:focus-visible, .vl-close:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
/* 44px and no smaller: these are the only controls on the site a visitor uses one-handed
   on a phone, and the touch-target floor in ACCESSIBILITY.md applies to them too. */
.vl-prev { left: 14px; top: calc(39vh - 22px); }
.vl-next { right: 14px; top: calc(39vh - 22px); }
.vl-close { right: 14px; top: 14px; font-size: 22px; }
@media (max-width: 700px) {
  .vl-img { max-width: 96vw; max-height: 68vh; }
  .vl-prev { left: 6px; }
  .vl-next { right: 6px; }
}

/* The airport card wears the same tinted square as every other card, with the airport's
   own three-letter code in it instead of a drawing. A plane was the obvious icon and the
   wrong one: all four cards would have carried the same plane, which is the decoration the
   rest of the set refuses to spend a badge on. NCE, CEQ, TLN and MRS each say which airport
   this is, which is the entire job of the mark.

   No `display` here on purpose — it inherits inline-flex from .icon-badge, which is what
   centres three letters in a 38px square. The padding offsets the trailing letter-space so
   they sit centred rather than a hair to the left. */
.airport-card .iata { font-size: 12px; font-weight: 800; letter-spacing: .08em; padding-left: .08em; margin-bottom: 9px; }
.airport-card h3 { margin: 0 0 6px; }
.airport-card .from { font-size: 14px; color: var(--ink-2); }

/* Both lines say the same thing: a grid item and a flex item may be narrower than their
   own content. Without them the row's min-content — padding, price, longest place name —
   set the list's width, and the Russian route names made a 322px page at 320px. */
.route-list { display: grid; gap: 8px; grid-template-columns: minmax(0, 1fr); }
.route-row > :first-child { min-width: 0; }
.route-row {
  display: flex; align-items: center; gap: 14px; text-decoration: none;
  padding: 13px 16px; border: 1px solid var(--line); border-radius: 12px; background: var(--bg);
  transition: border-color .15s, box-shadow .15s;
}
.route-row:hover { border-color: var(--accent); box-shadow: var(--shadow-sm); }
.route-row .name { font-weight: 650; }
.route-row .sub { font-size: 13px; color: var(--ink-2); }
/* Under 375px the row stops being a row. Side by side, the right-hand column refuses to be
   narrower than its own content and what is left goes to the name — which in Russian is
   "Ницца, / центр / города", three lines of two words. Below the phones that layout still
   works on, the distance and time drop under the name and take the full width, so every
   entry reads as one line of place and one of numbers.

   It used to be a price column here, and it was wider: "per vehicle · indicative" set the
   floor. Two short numbers need less, but the rule is unchanged because the failure it
   fixes is the long name, not the short column. */
@media (max-width: 374px) {
  .route-row { flex-wrap: wrap; gap: 4px 14px; }
  .route-row .price, .route-row .facts { margin-left: 0; width: 100%; text-align: left; display: flex; align-items: baseline; gap: 8px; }
  .route-row .price span, .route-row .facts span { display: inline; }
}

/* ---------------------------------------------------------------- the coast
   The home page's corridor drawing and the eight rows under it. See coastMap() in
   src/templates/pages.js for what is drawn and src/data/coast.js for the order.

   Everything here is coloured from the same variables as the rest of the page, which is
   the reason the drawing is inline SVG and not a file: an image would need a second copy
   for dark mode, would arrive after the HTML, and could not be a set of links. */
.coast { margin: 26px 0 0; }
/* Under about 900px the drawing stops fitting and starts scrolling rather than shrinking.
   A coast read west to east is the one diagram where a horizontal scroll is the natural
   gesture, and the alternative — 10px labels on a phone — is not a diagram at all. The
   links inside are reachable by tab whether or not anyone scrolls. */
.coast-scroll { overflow-x: auto; overscroll-behavior-x: contain; }
.coast-svg { display: block; width: 100%; min-width: 860px; height: auto; }
.coast-shore { stroke: var(--field-line); stroke-width: 2; stroke-linecap: round; }
.coast-tick { stroke: var(--line); stroke-width: 1.5; fill: none; }
.coast-spur { stroke: var(--field-line); stroke-width: 2; stroke-dasharray: 3 6; stroke-linecap: round; fill: none; }
.coast-dot { fill: var(--bg); stroke: var(--accent); stroke-width: 2.5; }
.coast-name { fill: var(--ink); font-size: 14px; font-weight: 650; text-anchor: middle; }
.coast-meta { fill: var(--ink-2); font-size: 12px; text-anchor: middle; font-variant-numeric: tabular-nums; }
.coast-compass { fill: var(--ink-2); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; text-anchor: start; }
.coast-compass-e { text-anchor: end; }
/* The airport is the accent, not the navy: on the tinted band in dark mode a navy pill is
   darker than the ground it sits on and reads as a hole rather than as the one fixed point
   every route on the drawing starts from. Accent-on-accent-ink clears AA in both modes. */
.coast-pill { fill: var(--accent); }
.coast-iata { fill: var(--accent-ink); font-size: 14px; font-weight: 800; letter-spacing: .06em; text-anchor: middle; }
.coast-cap { color: var(--ink-2); font-size: 13px; margin: 12px 0 0; }

.coast-routes { list-style: none; margin: 26px 0 0; padding: 0; }
.coast-routes li { margin: 0; }
.coast-routes .rr-text { display: block; }
.coast-routes .name, .coast-routes .blurb { display: block; }
.coast-routes .blurb { font-size: 13.5px; color: var(--ink-2); margin-top: 2px; }
.route-row .facts { margin-left: auto; text-align: right; white-space: nowrap; }
.route-row .facts b { font-size: 16px; font-variant-numeric: tabular-nums; }
.route-row .facts span { display: block; font-size: 12px; color: var(--ink-2); }
/* The same column, carrying a price instead of two figures, on the seven benchmarked routes.
   Slightly larger than .facts because it holds one number rather than two stacked ones. */
.route-row .price { margin-left: auto; text-align: right; white-space: nowrap; }
.route-row .price b { font-size: 17px; font-variant-numeric: tabular-nums; }
.route-row .price span { display: block; font-size: 11.5px; color: var(--ink-2); }
/* Two columns once there is room for the blurb to stay on one or two lines. Eight rows
   with a line of prose each is 600px of page in one column, and this section is a summary
   sitting above the full destination index rather than a replacement for it. */
@media (min-width: 960px) { .coast-routes { grid-template-columns: 1fr 1fr; } }

.step { counter-increment: step; position: relative; padding-left: 52px; }
.step::before {
  content: counter(step); position: absolute; left: 0; top: -2px;
  width: 36px; height: 36px; border-radius: 50%; background: var(--navy); color: #fff;
  display: grid; place-items: center; font-weight: 700; font-size: 16px;
}
/* Four across, which is one column per step and the shape this section has always had.
   It was tried at two — floor 380px, which breaks the row in half inside --maxw — and the
   pictures went from about 220px wide to about 505px, which is the difference between
   seeing that a booking form had been drawn and being able to read it. Four was chosen
   anyway: the section is a summary, it earns its place by being scannable in one screen,
   and two columns cost it roughly its own height again.
   So the pictures here are small on purpose. Widen this floor if that ever stops being the
   trade you want, and raise the `how` width in prepare-images.ps1 with it. */
.steps { counter-reset: step; display: grid; gap: 26px; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); }

/* The picture of each step, at the 3:2 its artwork was drawn at and cropped nowhere. The
   other photographed grids choose a shape and cut the master to it; these four are composed
   rather than photographed — a booking form, a confirmation, a driver card, a name board —
   and there is no subject to centre on, because the whole frame is the subject.

   The border is not decoration. Two of the four are line art on white, and this section
   sits on white as often as on the tint, so without an edge they would bleed into the page
   and read as a broken image. */
.step-photo {
  display: block; width: 100%; aspect-ratio: 3 / 2; object-fit: cover;
  border: 1px solid var(--line); border-radius: 12px;
  margin-bottom: 15px; background: var(--bg-2);
}


.pill { display: inline-block; font-size: 11.5px; font-weight: 650; padding: 3px 9px; border-radius: 999px; background: var(--bg-3); color: var(--ink-2); }

details.faq { border: 1px solid var(--line); border-radius: 12px; padding: 0; margin-bottom: 9px; background: var(--bg); }
details.faq summary { cursor: pointer; padding: 15px 18px; font-weight: 650; list-style: none; display: flex; align-items: center; gap: 12px; }
details.faq summary::-webkit-details-marker { display: none; }
details.faq summary::after { content: "+"; margin-left: auto; font-size: 21px; color: var(--ink-2); font-weight: 400; line-height: 1; }
details.faq[open] summary::after { content: "−"; }
details.faq .a { padding: 0 18px 16px; color: var(--ink-2); }

/* --bg-3, not --bg-2: a callout is highlighted information, and on a tinted section a
   --bg-2 callout was the colour of the band it sat in. */
.callout { background: var(--bg-3); border-left: 3px solid var(--accent); border-radius: 0 12px 12px 0; padding: 16px 20px; }
.verdict { background: var(--bg-3); border-left: 3px solid var(--accent); border-radius: 0 12px 12px 0; padding: 18px 22px; }
.verdict b { display: block; margin-bottom: 4px; }

.prose h2 { margin-top: 1.6em; }
.prose h3 { margin-top: 1.4em; }
.prose > p, .prose > ul { max-width: 68ch; }

/* ---------------------------------------------------------------- footer */
.site-footer { background: var(--navy); color: rgba(255,255,255,.76); padding: 48px 0 26px; margin-top: 20px; }
/* contact@niceairporttransfer.net is 203px of unbreakable token, and the five footer columns
   are at their narrowest just above 1040px. It has to be allowed to break mid-word or it
   sticks out of the page and takes a horizontal scrollbar with it — `anywhere` rather than
   `break-word` because break-word will not split a word that is alone in its box, which is
   exactly this case. */
.site-footer a { color: rgba(255,255,255,.76); text-decoration: none; overflow-wrap: anywhere; }
.site-footer a:hover { color: #fff; text-decoration: underline; }
.site-footer h3 { color: #fff; font-size: 13px; text-transform: uppercase; letter-spacing: .07em; margin-bottom: 12px; }
.footer-grid { display: grid; grid-template-columns: 1.5fr repeat(4, 1fr); gap: 30px; }
.footer-grid ul { list-style: none; padding: 0; margin: 0; }
.footer-grid li { margin-bottom: 9px; font-size: 14.5px; }
/* The profile row — icons, no text, so sizing is the whole accessibility story here, and
   the 44px box around a 20px glyph is the only part of it worth defending.
   44x44 is the site's own floor, not a rule it is held to: 2.5.5 is AAA and this site
   audits at AA, and 2.5.8's AA minimum is 24. It is 44 because that is what the mobile-bar
   buttons were sized to, and because a link with no text is a link you cannot aim at by
   reading it. The gap is 4px rather than 0 so two adjacent targets cannot be taken for one,
   and so the focus ring — 3px at 2px offset from the global rule, inverted for the footer —
   has somewhere to land. Transparent until hover: a row of filled buttons in a column of
   plain text links would be the loudest thing in the footer rather than the quietest.
   `text-decoration: none` on hover because .site-footer a:hover underlines, and an
   underline under a graphic draws a stray 20px rule.
   THE COST, measured at 320px: the contact column is 128px wide there, so the icons wrap
   two to a line instead of sitting in one row. Four accounts make a 2x2 block. That is the
   same trade the mobile-bar labels were given — full-size targets wrapped beat shrunken
   targets in a line — and it is a layout the footer grid imposes, not this rule: widening
   that column on phones would fix it and would change five other things with it. */
.social-row { list-style: none; padding: 0; margin: 10px 0 0; display: flex; flex-wrap: wrap; gap: 4px; }
.social-row li { margin: 0; }
.social-row a { display: grid; place-items: center; width: 44px; height: 44px; border-radius: 10px; }
/* The platform's own colour, set per link as --brand in layout.js and measured there against
   --navy for 1.4.11. The fallback is the footer's text colour, which is what a platform with
   no colour of its own gets. It rides on the glyph only: the hover plate below stays white. */
.social-row a { color: var(--brand, currentColor); }
.social-row a:hover { background: rgba(255,255,255,.1); text-decoration: none; }
.social-row svg { width: 20px; height: 20px; display: block; }
/* Browsers still italicise <address>; this is a postal address, not an aside. */
.footer-address { font-style: normal; margin: 10px 0 0; line-height: 1.5; }

.footer-bottom { border-top: 1px solid rgba(255,255,255,.13); margin-top: 34px; padding-top: 18px; font-size: 13px; display: flex; flex-wrap: wrap; gap: 12px; }
@media (max-width: 1040px) { .footer-grid { grid-template-columns: repeat(3, 1fr); gap: 26px; } }
@media (max-width: 800px) { .footer-grid { grid-template-columns: 1fr 1fr; gap: 24px; } }
/* Stops the footer pushing the page sideways below about 340px. A grid item defaults to
   min-width:auto, so the first column refused to go under 193px — the nowrap brand name —
   and two columns asked for 327px inside 280px of .wrap. Same clause of 1.4.10 as the
   .mobile-bar fix. */
.footer-grid > div { min-width: 0; }
.site-footer .logo { white-space: normal; }

/* ---------------------------------------------------------------- the drivers page
   Both blocks are absent until somebody photographs the drivers, so none of this renders
   on a default build. The portraits are square because faces are, and because a grid of
   mixed crops reads as a mixed team rather than one. `cover`, like the fleet photographs
   and for the same reason: a contained portrait letterboxes and looks like a mistake. */
.driver-band {
  margin: 0; aspect-ratio: 2 / 1; overflow: hidden;
  border-radius: var(--radius); background: var(--bg-3);
  max-width: var(--maxw); margin-inline: auto;
}
.driver-band img { width: 100%; height: 100%; object-fit: cover; display: block; }
@media (max-width: 700px) { .driver-band { border-radius: 0; aspect-ratio: 3 / 2; } }

.driver-grid {
  margin-top: 22px; display: grid; gap: 20px;
  grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr));
}
.driver-card { margin: 0; text-align: center; }
.driver-card img {
  /* Capped, because auto-fit gives a single item the whole row: with one driver configured
     the portrait rendered as a 1140px circle. The cap makes one, three or nine look like the
     same page. */
  width: 100%; max-width: 190px; margin-inline: auto;
  aspect-ratio: 1 / 1; object-fit: cover; display: block;
  border-radius: 50%; background: var(--bg-3); border: 1px solid var(--line);
}
.driver-card figcaption { margin-top: 10px; line-height: 1.35; }
.driver-card figcaption span { display: block; margin-top: 2px; }

/* The marina cards on /yacht-transfers/. Landscape, not the drivers' circles: these are
   places, and a place reads wrong in a portrait crop. Same `cover` rule and the same cap,
   so one marina and five look like the same page. */
.marina-grid {
  margin-top: 22px; display: grid; gap: 18px;
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
}
.marina-card { margin: 0; }
.marina-card img {
  width: 100%; max-width: 420px; aspect-ratio: 16 / 9; object-fit: cover; display: block;
  border-radius: 12px; background: var(--bg-3); border: 1px solid var(--line);
}
.marina-card figcaption { margin-top: 9px; font-weight: 650; font-size: 15px; }

/* The Google Maps link on a route page. Understated on purpose: it is a useful check, not a
   call to action, and it is the one link on the page that sends somebody away from it. */
.map-link { margin: 14px 0 0; font-size: 15px; }
.map-link a { display: inline-flex; align-items: center; gap: 8px; color: var(--accent); text-decoration: none; font-weight: 600; }
.map-link a:hover { text-decoration: underline; }
.map-link svg { width: 17px; height: 17px; flex: none; }

/* ---------------------------------------------------------------- floating WhatsApp
   Desktop only. The sticky mobile bar already carries a full-width WhatsApp button below
   768px, so this hides at exactly the width that one appears and the two are never both on
   screen. Above it, a visitor who has scrolled past the booking card previously had no way
   to reach WhatsApp short of the footer.

   Colours are the pair the buttons already use — #05331b on --wa, which measures 7.08:1.
   White on that green is 1.98:1 and is the obvious mistake to make here. The label is
   visually hidden rather than dropped, so the link has a real accessible name; the focus
   ring comes from the global :focus-visible rule. */
.vh {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.wa-float {
  position: fixed; right: 22px; bottom: 22px; z-index: 55;
  width: 56px; height: 56px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--wa); color: #05331b;
  box-shadow: 0 6px 20px rgba(11, 31, 51, .28);
  transition: transform .18s ease, box-shadow .18s ease;
}
.wa-float svg { width: 27px; height: 27px; }
.wa-float:hover { transform: translateY(-2px); box-shadow: 0 10px 26px rgba(11, 31, 51, .34); }
@media (max-width: 768px) { .wa-float { display: none; } }

/* ---------------------------------------------------------------- consent
   Only rendered when google.placesApiKey is set. The two buttons are deliberately the same
   size, weight and colour: the CNIL's position is that refusing must be as easy as
   accepting, and an "Allow" in the accent with a grey "Refuse" beside it fails that in
   substance whatever the markup says. Neither is the visually obvious answer.

   Sits above the mobile bar and the floating WhatsApp button, because a choice that a
   sticky element covers is not a choice. */
.consent {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 90;
  background: var(--bg); border-top: 1px solid var(--field-line);
  box-shadow: 0 -8px 30px rgba(11, 31, 51, .16);
}
.consent[hidden] { display: none; }
.consent-in {
  max-width: var(--maxw); margin: 0 auto; padding: 16px 20px;
  display: flex; gap: 20px; align-items: center; justify-content: space-between; flex-wrap: wrap;
}
.consent h2 { font-size: 16px; margin: 0 0 4px; }
.consent p { margin: 0; font-size: 14px; color: var(--ink-2); max-width: 68ch; line-height: 1.5; }
.consent p a { color: var(--accent); }
.consent-btns { display: flex; gap: 10px; flex: none; }
.consent-btns .btn {
  min-width: 116px; justify-content: center;
  background: var(--bg-3); color: var(--ink); border: 1.5px solid var(--field-line); font-weight: 650;
}
.consent-btns .btn:hover { background: var(--bg-2); }
@media (max-width: 700px) {
  .consent-in { padding: 14px 16px; gap: 14px; }
  .consent-btns { width: 100%; }
  .consent-btns .btn { flex: 1; min-width: 0; }
  body:has(.consent:not([hidden])) .mobile-bar { bottom: 0; }
}

/* ---------------------------------------------------------------- sticky mobile bar */
.mobile-bar { display: none; }
@media (max-width: 768px) {
  .mobile-bar {
    display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
    box-shadow: 0 -4px 20px rgba(11, 31, 51, .22);
  }
  .mobile-bar a {
    /* min-width:0 is the fix for 1.4.10: a flex item defaults to min-width:auto, so
       "WhatsApp" plus its icon refused to shrink below 122px and three of them forced a
       340px page inside a 320px viewport. The label truncates instead of the page
       scrolling sideways. */
    flex: 1 1 0; min-width: 0;
    display: flex; align-items: center; justify-content: center; gap: 8px;
    min-height: 56px; padding: 14px 8px; font-size: 16px; font-weight: 650;
    text-decoration: none; color: #fff;
  }
  /* The label must never be clipped. This is the primary call to action on a phone, and
     "Get your pri…" is worse than no bar at all — so below 400px the words wrap onto a
     second line inside the same 56px button instead of truncating. Two short lines of 13px
     still clear the 24px target and read as a whole phrase; an ellipsis does not. */
  .mobile-bar a span { min-width: 0; }
  @media (max-width: 400px) {
    .mobile-bar a { font-size: 13.5px; padding: 8px 5px; gap: 6px; line-height: 1.15; }
    .mobile-bar a span { text-align: center; }
    .mobile-bar svg { width: 18px; height: 18px; }
  }
  .mobile-bar svg { width: 20px; height: 20px; }
  /* --accent-ink, not the bar's white. In dark mode --accent is the light cyan #35b5cf
     and white on it measures 2.42:1 — the primary call to action on a phone, failing
     1.4.3 in the one mode the sticky bar is always on screen in. --accent-ink is white in
     light mode and #04222c in dark, which is the pair .btn-primary has always used. */
  .mobile-bar .mb-price { background: var(--accent); color: var(--accent-ink); }
  .mobile-bar .mb-call { background: var(--navy); }
  .mobile-bar .mb-wa { background: var(--wa); color: #05331b; }
  body { padding-bottom: 56px; }
}

.breadcrumb { font-size: 13.5px; color: var(--ink-2); padding: 14px 0 0; }
.breadcrumb a { color: var(--accent); text-decoration: none; }
.breadcrumb a:hover { text-decoration: underline; }


/* ---------------------------------------------------------------- focus
   Before this, exactly two things on the site had a focus style: the skip link and the
   fields inside the booking card. Everything else — every button, every link, the language
   switch, the sticky mobile bar — fell back to whatever ring the browser draws, which on
   the accent button, the WhatsApp green and the navy footer is close to invisible.

   Two layers rather than one, because no single colour survives all four of those
   backgrounds: an --ink line with a --bg halo outside it. On the white bands the line
   carries it; on the navy footer and the coloured buttons the line disappears and the halo
   carries it. One of the two always contrasts, which is what 1.4.11 asks for.

   :focus-visible, not :focus, so clicking a button with a mouse does not leave a ring
   sitting on it afterwards. */
:focus-visible {
  outline: 3px solid var(--ink);
  outline-offset: 2px;
  box-shadow: 0 0 0 6px var(--bg);
}
.site-footer :focus-visible, .mobile-bar :focus-visible {
  outline-color: var(--bg);
  box-shadow: 0 0 0 6px var(--ink);
}

/* Motion: there are only three transitions on the site, but honouring the preference costs
   one rule and the alternative is overriding somebody's OS setting. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
}

.skip { position: absolute; left: -9999px; }
.skip:focus { left: 12px; top: 12px; z-index: 100; background: var(--bg); padding: 10px 16px; border-radius: 8px; border: 2px solid var(--accent); }

@media print { .site-header, .mobile-bar, .quote-card, .site-footer { display: none; } }


/* ---------------------------------------------------------------- event weeks
   The one block on this site no competitor page carries. It is set as cards rather than
   as a warning banner on purpose: a banner reads as a disclaimer to be dismissed, and this
   is information the visitor came for. The left rule in the brand's sun colour is the only
   decoration it gets — enough to mark it as different from the six service cards above it,
   not enough to look like an alert. */
/* One height for all eight, not one height per row.
   A grid row already stretches its own cards to match — that part was never the problem.
   What varied was row against row: the Grand Prix entry runs to 481px and MIPCOM to 392px,
   so the second and third rows sat visibly shorter than the first. `grid-auto-rows: 1fr`
   makes every row take the tallest card on the page, so the eight boxes read as one set.
   The cost is real and is the reason this is not the default: the shortest entries gain
   roughly ninety pixels of empty space below their last line. Delete this rule to go back
   to rows that size themselves. */
.event-grid { grid-auto-rows: 1fr; }

.event-card { border-left: 3px solid var(--accent); }
.event-card h3 { margin-bottom: 6px; }
.event-card .event-when { margin: 0 0 10px; }
.event-card p + p { margin-top: 10px; }

/* ── the rate ladder on a benchmarked route page ─────────────────────────────────────────
   Restored with the prices in 2026-10. The table scrolls sideways rather than squeezing:
   three columns of which one is a photograph and one a figure do not compress below about
   440px, and a horizontal scroll inside the block is better than a cramped unreadable one. */
.spec-table { width: 100%; border-collapse: collapse; }
.spec-table th, .spec-table td { padding: 12px 14px; border-bottom: 1px solid var(--line); text-align: left; font-size: 15px; }
.spec-table th { font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-2); font-weight: 650; }
.spec-table td.num { text-align: right; font-variant-numeric: tabular-nums; font-weight: 650; }
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

.veh-cell { display: flex; align-items: center; gap: 12px; }
.veh-thumb {
  flex: none; width: 76px; aspect-ratio: 16 / 9; display: grid; place-items: center;
  background: #fff; border-radius: 6px; overflow: hidden;
}
.veh-thumb img { width: 100%; height: 100%; object-fit: contain; display: block; }
.veh-thumb.has-photo img { object-fit: cover; }
@media (max-width: 560px) { .veh-thumb { width: 58px; } }
