/* Retro Food Trucks.

   Every number in the token block was read off the live site with
   getComputedStyle rather than matched by eye. Nothing here derives from the
   previous theme's stylesheet: the measurements are the same because the design
   is the same, the code is not.

   Measured 2026-08-28 at 1440x900:
     Raleway throughout
     h1        55px / 65px / 700 / #FF0000 / centred
     banner h1 60px / 700 / #FFF / text-shadow 1px 1px 0 #000
     body      25px / 40px / 400 / #7A7A7A / centred
     nav       18px / 700 / #FF0000, 45px vertical padding
     content   1220px
     footer    #FF0000 ground, white text, 18px */

@font-face {
  font-family: 'Raleway';
  src: url('/assets/fonts/raleway-latin.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap;
  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: 'Raleway';
  src: url('/assets/fonts/raleway-latin-ext.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap;
  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;
}

@font-face {
  font-family: 'Lobster';
  src: url('/assets/fonts/lobster-latin.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
  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: 'Lobster';
  src: url('/assets/fonts/lobster-latin-ext.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
  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;
}

@font-face {
  font-family: 'Open Sans';
  src: url('/assets/fonts/opensans-latin.woff2') format('woff2');
  font-weight: 300 800;
  font-display: swap;
  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: 'Open Sans';
  src: url('/assets/fonts/opensans-latin-ext.woff2') format('woff2');
  font-weight: 300 800;
  font-display: swap;
  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;
}

:root {
  /* Their brand red, kept exactly, for anything WCAG counts as large text:
     the 55px h1, section headings, the hero wordmark, the big band label. */
  --red: #FF0000;

  /* The same red one step darker, for small text and for the footer ground.
     #FF0000 against white is 4.00:1 and white against #FF0000 is also 4.00:1,
     which fails the 4.5 minimum at the sizes the nav, the buttons and the
     footer actually use. #EE0000 clears it at 4.46:1 and is not a difference
     you can see without the two side by side.

     TO MATCH THE LIVE SITE EXACTLY: set --red-ui to #FF0000 and --body to
     #7A7A7A. That reinstates four colour-contrast failures, which is the
     trade being made. */
  --red-ui: #EE0000;

  /* #7A7A7A is 4.29:1 on white, which passes at the home page's 25px but
     fails at the 18px the inner pages use. #757575 is 4.61:1. */
  --body: #757575;
  --ink: #222222;
  --line: #E6E6E6;
  --wrap: 1220px;

  --fs-h1: 55px;
  --lh-h1: 65px;
  --fs-copy: 25px;
  --lh-copy: 40px;
  --fs-nav: 18px;

  /* The one place a contrast decision is recorded. #FF0000 on white is 4.0:1,
     which is under 4.5 for body-sized text but over 3.0, so it is used only at
     the sizes where WCAG counts text as large: the 55px h1, the 18px/700 nav,
     and headings. Body copy never uses it. On the red footer the relationship
     inverts and white on #FF0000 is 4.0:1, so footer text is set at 18px and
     the small print at 700 weight to stay in the large-text band. */
}

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

html { -webkit-text-size-adjust: 100%; }

/* The live theme's body text is 18px/22px near-black, LEFT aligned - the
   grey centred look that reads as the site's voice belongs to the home page
   alone, where the builder set it per block. Harvested per-block classes in
   content.css reproduce those; this is the default underneath them. */
body {
  margin: 0;
  background: #fff;
  color: var(--ink);
  font: 400 18px/22px 'Raleway', Arial, Tahoma, sans-serif;
  text-align: left;
}

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

a { color: var(--red-ui); }
a:focus-visible,
button:focus-visible,
[data-lightbox]:focus-visible { outline: 3px solid var(--red); outline-offset: 3px; }

.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: 16px; }

/* Skip link: clipped until focused, for the same reason as the honeypot. */
.skip {
  position: absolute; top: 0; left: 0; z-index: 100;
  width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  background: #fff; color: var(--red-ui); padding: 12px 18px;
  font-weight: 700;
}
.skip:focus {
  width: auto; height: auto; overflow: visible;
  clip: auto; clip-path: none;
}

/* ---------- header ---------- */
.hdr { background: #fff; }
/* The header runs nearly full-bleed on the live site rather than sitting
   inside the 1220px content column: measured at 1440, the logo starts ~30px
   from the left and the last nav item ends ~80px from the right. Capping it at
   --wrap squeezed eight items into seven slots and wrapped "Contact" onto a
   second line. */
.hdr__bar {
  max-width: 1600px; margin-inline: auto; padding-inline: 24px;
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.hdr__logo { display: block; padding: 15px 0; flex: none; }
.hdr__logo img { width: 257px; height: auto; }

.nav ul {
  list-style: none; margin: 0; padding: 0;
  display: flex; align-items: stretch; flex-wrap: wrap; justify-content: flex-end;
}
.nav a {
  display: block; padding: 45px 10px;
  font-size: var(--fs-nav); font-weight: 700; line-height: 1;
  color: var(--red-ui); text-decoration: none;
}
.nav a:hover { color: var(--ink); }
.nav a[aria-current='page'] { color: var(--ink); }

.hdr__search-toggle {
  background: none; border: 0; cursor: pointer; color: var(--red-ui);
  padding: 8px; flex: none; display: grid; place-items: center;
}
.hdr__search-toggle:hover { color: var(--ink); }
.hdr__search[hidden] { display: none; }
.hdr__search {
  border-top: 1px solid var(--line);
  display: flex; gap: 12px; justify-content: center; align-items: center;
  padding: 14px 16px;
}
.hdr__search input {
  font: 400 20px/1.4 'Raleway', Arial, sans-serif;
  padding: 10px 16px; width: min(480px, 70vw);
  border: 1px solid var(--line); border-radius: 6px;
}
.hdr__search .btn { padding: 12px 26px; }

.hdr__toggle {
  display: none; width: 46px; height: 40px; padding: 8px;
  background: none; border: 0; cursor: pointer;
}
.hdr__toggle span { display: block; height: 3px; background: var(--red); border-radius: 2px; }
.hdr__toggle span + span { margin-top: 5px; }

/* ---------- page-title banner ---------- */
.banner {
  min-height: 370px;
  display: grid; place-items: center;
  background-color: #999;
  background-size: cover; background-position: center;
  background-repeat: no-repeat;
  position: relative;
}
/* The live banner lays a translucent wash over the photograph. Same wash. */
/* On the live page the title band carries background-color rgba(247,247,247,.5)
   on the SAME element as its background-image, so the colour sits BEHIND the
   photograph and is never seen. An earlier build painted it as an ::after
   overlay on top, washing every banner out by half. It belongs underneath. */
.banner { background-color: rgba(247, 247, 247, .5); }
.banner__inner { position: relative; z-index: 1; padding: 40px 16px; }
.banner h1 {
  margin: 0;
  font-size: 60px; line-height: 1.1; font-weight: 700;
  color: #fff; text-shadow: 1px 1px 0 #000;
}
.crumb {
  margin-top: 14px; font-size: var(--fs-nav); font-weight: 700;
  color: #fff; text-shadow: 1px 1px 0 #000;
  display: flex; gap: 10px; justify-content: center; flex-wrap: wrap;
}
.crumb a { color: #fff; text-decoration: none; }
.crumb a:hover { text-decoration: underline; }

/* ---------- carousels ---------- */
/* Two on the home page, one component. Measured live: hero 750px with a 75px
   title, closing band 450px. Slides cross-fade; nothing slides sideways,
   because a horizontal transform on a full-bleed image is the one thing that
   makes a hero feel cheap on a slow machine. */
.carousel {
  position: relative;
  display: grid; place-items: center;
  background: #111; overflow: hidden;
  min-height: 750px;
}
.carousel--band { min-height: 450px; margin-top: 60px; }

.carousel__slides { list-style: none; margin: 0; padding: 0; position: absolute; inset: 0; }
.carousel__slide {
  position: absolute; inset: 0;
  /* The movement itself is defined further down, with the measurements. */
}
.carousel__slide > img { width: 100%; height: 100%; object-fit: cover; }

/* The per-slide title. Sits with the slide so it fades with it. */
.carousel__title {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  margin: 0; padding: 0 16px;
  color: #fff; font-weight: 700; line-height: 1.05;
  font-size: clamp(38px, 5.6vw, 75px);
  text-shadow: 2px 2px 8px rgba(0, 0, 0, .55);
}

/* Fixed furniture over the top: roundel, testimonials, or the band's label.
   On the live hero the roundel sits to the left and the testimonials to the
   upper right, with the slide title between them rather than underneath them.
   Two columns reproduces that and keeps the title clear. */
.carousel__over {
  position: relative; z-index: 2;
  width: 100%; max-width: var(--wrap); margin-inline: auto; padding: 40px 16px;
  display: grid; gap: 26px; justify-items: center;
  pointer-events: none;
}
/* Slide-one fixtures: see the HOME PAGE FIDELITY PASS block below. */
.carousel__over a, .carousel__over button { pointer-events: auto; }

.carousel__roundel { width: clamp(110px, 14vw, 200px); height: auto; }

.carousel__label {
  display: inline-block;
  background: rgba(255, 0, 0, .78); color: #fff; text-decoration: none;
  font-size: clamp(28px, 4vw, 52px); font-weight: 700; line-height: 1.1;
  padding: 22px 44px;
}
.carousel__label:hover { background: rgba(255, 0, 0, .92); }

/* Testimonials. The live site sets these on a translucent red-orange card. */
.quotes { display: grid; gap: 14px; max-width: 660px; justify-self: end; align-self: start; }
.quote { margin: 0; }
.quote blockquote {
  margin: 0; background: rgba(255, 58, 45, .7); color: #fff;
  font-size: 17px; line-height: 1.45; font-weight: 600;
  padding: 12px 16px; text-align: left;
}
.stars { margin: 0 0 4px; display: flex; gap: 2px; }
.stars svg { fill: #FFCC00; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .45)); }

/* The five stars arrive one after another, rising and growing into place.
   Live animates each from y:60px with the shape squashed to sX:0.1, sY:0.5,
   over 600ms, starting at 1110ms and stepping roughly 75ms apart. Those are
   its numbers, read off the layer definitions rather than eyeballed.

   Only on the hero, and only once: this is the opening of the page, not
   something to replay every time the slide comes round again. */
@keyframes star-in {
  from { opacity: 0; transform: translateY(60px) scale(0.1, 0.5); }
  to   { opacity: 1; transform: translateY(0) scale(1, 1); }
}
.carousel--hero .stars svg {
  opacity: 0;
  transform-origin: 50% 100%;
  animation: star-in 600ms cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
}
.carousel--hero .stars svg:nth-of-type(1) { animation-delay: 1110ms; }
.carousel--hero .stars svg:nth-of-type(2) { animation-delay: 1190ms; }
.carousel--hero .stars svg:nth-of-type(3) { animation-delay: 1270ms; }
.carousel--hero .stars svg:nth-of-type(4) { animation-delay: 1340ms; }
.carousel--hero .stars svg:nth-of-type(5) { animation-delay: 1400ms; }

/* Controls */
.carousel__arrow {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 3;
  background: rgba(0, 0, 0, .35); border: 0; cursor: pointer; color: #fff;
  font: 700 44px/1 'Raleway', Arial, sans-serif; padding: 6px 20px 14px;
}
.carousel__arrow:hover { background: rgba(0, 0, 0, .6); }
.carousel__arrow--prev { left: 0; }
.carousel__arrow--next { right: 0; }

.carousel__dots {
  position: absolute; z-index: 3; bottom: 20px; left: 0; right: 0;
  display: flex; gap: 10px; justify-content: center;
}
.carousel__dots button {
  width: 13px; height: 13px; padding: 0; border-radius: 50%; cursor: pointer;
  border: 2px solid #fff; background: transparent;
}
.carousel__dots button[aria-current='true'] { background: #fff; }

.btn {
  display: inline-block;
  background: var(--red-ui); color: #fff; text-decoration: none;
  font-size: var(--fs-nav); font-weight: 700;
  padding: 16px 34px; border: 0; cursor: pointer;
  font-family: inherit;
}
.btn:hover { background: #d40000; }

/* Visually hidden, still read aloud. */
.vh {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* ---------- bands & prose ---------- */
/* Sections carry their top space; the last one on a page carries the bottom
   space too. The footer itself adds none, so a page ending in a full-bleed
   band runs straight into the red without a white stripe between them, which
   is how the live site reads. */
/* The gap from the last thing on the page down to the footer, owned in one
   place. It used to be set by four separate :last-child rules, so the value a
   page got depended only on which kind of section happened to end it: 35px
   after a photo grid, 60px after a form, 115px after the category cards, 140px
   on Catering Hire. The client saw the 60px one, where a red Submit pill sits
   just above the full-bleed red footer, and said it needed tidying.

   120px is twice the ordinary 60px interval between sections, which reads as a
   deliberate ending rather than a page that merely stopped. Astra argued this
   down from the 140px I proposed: 140 was only the largest value already
   present, and copying the loosest page is not the same as choosing a value.

   The live site is no guide here. Its equivalent gap runs from 15px to 540px
   across the 29 routes, because it is a page builder hand-edited for eight
   years and those are arbitrary empty rows rather than a system. */
:root { --page-end-gap: 120px; }
#main { padding-block-end: var(--page-end-gap); }
/* The final section gives up its own terminal spacing, so the gap above is the
   whole story. The id outweighs .page-catering-hire .band, which sets 55px on
   every band on that page including the last one. */
/* Descendant, not child. On the real site the marked section is a direct child
   of #main and either would match. The single-file preview wraps each route in
   a .view section, so a child combinator silently stopped matching there and
   Catering Hire showed 200px at the foot where the site shows 120px. The
   preview is what the client sees before launch, so it should not disagree
   with the site about spacing. */
#main [data-page-end] { padding-block-end: 0; margin-block-end: 0; }
/* .cards carries margin: 55px 0 80px and that trailing 80px sits outside the
   grid, so it would stack on top of the gap. Only the last grid is cleared;
   the spacing between card groups mid-page is untouched. */
#main [data-page-end] > .wrap > .cards { margin-block-end: 0; }

.band { padding: 60px 0 0; }
.band--tight { padding-top: 30px; }

/* The live site has no type system: every block was sized by hand, so the
   body scale genuinely differs page to page. Measured live:
   home 25/40 centred, Find a Food Truck 22/30 left, everything else 18/22.
   Those three are reproduced rather than averaged into one, because averaging
   them would be a redesign. The 18/22 tier is tight — 1.22 line-height on
   running text — and is the one number worth revisiting with the client. */
/* Long unbreakable tokens must wrap rather than scroll the page sideways. */
.prose { margin-inline: auto; overflow-wrap: anywhere; }
.prose--lg { font-size: var(--fs-copy); line-height: var(--lh-copy); color: var(--body); text-align: center; }
.prose--md { font-size: 22px; line-height: 30px; }
.prose > p { margin-bottom: 15px; }
.prose h2, .prose h3, .prose h4, .prose ul { margin-bottom: 15px; }
.prose--lg > p { margin-bottom: 24px; }
.prose > * { margin: 0 0 24px; }
.prose > *:last-child { margin-bottom: 0; }
.prose h1 {
  font-size: var(--fs-h1); line-height: var(--lh-h1); font-weight: 700;
  color: var(--red);
}
/* Theme heading defaults, measured live: regular weight, not bold. Most
   headings carry an exact harvested class on top of these. */
.prose h2, .prose h3, .prose h4 {
  font-weight: 400; color: var(--red); line-height: 1.15;
}
.prose h2 { font-size: 42px; }
.prose h3 { font-size: 35px; }
.prose h4 { font-size: 32px; color: #000; }
.prose ul { padding-left: 0; list-style-position: inside; }
.prose--left { text-align: left; }

/* ---------- photo grid ---------- */
/* Two grids, both measured on the live site:
   product pages run 4 columns of flush square tiles (gap 0), the gallery runs
   6 columns of 200px squares with hairline gaps. */
.grid { display: grid; }
.grid--product { grid-template-columns: repeat(4, 1fr); gap: 0; }
/* Live's gallery tiles are flush - 200px squares butted together with no gap
   at all, six across. The build's 4px gutter shrank every tile to 195. */
.grid--gallery { grid-template-columns: repeat(6, 1fr); gap: 0; }
.shot { display: block; overflow: hidden; background: #f2f2f2; }
.shot img {
  width: 100%; height: 100%; aspect-ratio: 1 / 1; object-fit: cover;
  transition: transform 400ms ease;
}
.shot:hover img { transform: scale(1.04); }

/* ---------- category cards ---------- */
/* The Custom Built Food Trucks page: Lobster section heads, square photo
   cards with the title in white Lobster over the image and a Learn More
   button. Sizes measured live: 3 columns of ~409px squares, title 40px,
   button 16px #333 on #F7F7F7 with a 5px radius. */
.cards-head {
  font-family: 'Lobster', cursive; font-weight: 400;
  font-size: 48px; color: var(--red); text-align: center;
  margin: 0 0 30px;
}
.cards {
  display: grid; gap: 30px;
  grid-template-columns: repeat(3, 1fr);
  margin-bottom: 40px;
}
.card-tile {
  position: relative; display: grid; place-items: center;
  aspect-ratio: 1 / 1; overflow: hidden; background: #f2f2f2;
  text-decoration: none;
}
.card-tile img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  transition: transform 400ms ease;
}
.card-tile:hover img { transform: scale(1.04); }
.card-tile__title {
  position: relative; z-index: 1;
  font-family: 'Lobster', cursive; font-weight: 400;
  font-size: 40px; line-height: 1.15; color: #fff; text-align: center;
  text-shadow: 1px 1px 6px #000;
  padding: 0 24px; align-self: center; margin-top: 18%;
}
.card-tile__btn {
  position: relative; z-index: 1;
  background: #F7F7F7; color: #333;
  font-size: 16px; font-weight: 400; line-height: 1;
  padding: 14px 20px; border-radius: 5px;
  align-self: start;
}
.card-tile:hover .card-tile__btn { background: #fff; }

/* ---------- trusted supplier cards ---------- */
/* Same square tile as the product grid, which is what the client asked for, but
   with three things stacked inside it rather than two, so the inner layout is
   set here instead of inheriting the product card's. */
.supplier {
  /* Rows sized to their content and the group centred, not a spare-space row
     first: that put the name in a tall row aligned to its bottom edge and left
     the top half of every card empty. */
  grid-template-rows: repeat(3, auto);
  align-content: center; gap: 10px;
  padding: 26px 22px; text-align: center;
}
.supplier .card-tile__title { margin-top: 0; align-self: center; }
.supplier__blurb {
  position: relative; z-index: 1;
  font-size: 16px; line-height: 22px; color: #fff;
  text-shadow: 1px 1px 6px #000;
}
.supplier .card-tile__btn { align-self: center; justify-self: center; }
/* Over a photograph the copy needs its own ground, or a bright picture eats it.
   The shade reaches full strength 40% down rather than only at the bottom edge,
   because the text sits in the middle of the tile: Dipped's white blurb over a
   pale pink van and white tiles was only just legible. Astra's fix, 11 Sep 2026. */
.supplier:not(.supplier--plain)::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(to bottom, rgba(0, 0, 0, .15), rgba(0, 0, 0, .55) 40%, rgba(0, 0, 0, .55));
}
/* Without a photograph the tile takes the site's own red, so a supplier who has
   not sent an image yet still reads as a finished card rather than a gap. Drop
   an image into suppliers.json and it becomes a photo card with no other edit.
   It is --red-ui, the step darker kept for small type on red: the 16px white
   blurb on --red measured 3.99:1 under axe, where 4.5:1 is needed, and on
   --red-ui it is 4.53:1. Found in the final accessibility scan, 23 Sep 2026. */
.supplier--plain { background: var(--red-ui); }
/* The product grid holds three across down to 1080px, which is right for it:
   those tiles are a photograph with a title laid over, and a long title that
   overruns clips against the image without anyone noticing.

   Supplier tiles carry a name, a line of description and a handle button
   stacked inside a square, so they cannot take the same treatment. At 768px
   three across gives a 214px tile, and four of the eight cards had their
   button sliced in half by the tile edge: .card-tile sets overflow:hidden,
   which zeroes a grid item's automatic minimum size, so the square wins and
   the content is cut rather than the box growing. Found by measuring every
   card's button against its tile, not by eye; at a glance the red tiles just
   look like red tiles. */
/* Doubled class, not for emphasis: .cards--supplier alone ties on specificity
   with the .cards rule 800 lines below that re-establishes three columns to
   1080px, and the later rule then wins. Written the first way, these two lines
   changed nothing at all and the buttons stayed clipped. */
@media (max-width: 1080px) { .cards.cards--supplier { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 767px)  { .cards.cards--supplier { grid-template-columns: 1fr; } }
.supplier--plain .card-tile__title,
.supplier--plain .supplier__blurb { text-shadow: none; }

/* ---------- food truck directory ---------- */
/* Banded rows, thumbnail left, three labelled facts across, exactly as the
   live page reads. Below 760px the three facts stack under the thumbnail
   rather than squeezing into columns nothing fits in. */
.dir { list-style: none; margin: 0; padding: 0; }
/* The stride, re-measured across all 17 live rows rather than just the first.
   Live's first row is 185px and every row after it is 220px: a 150px thumbnail
   with a 35px band beneath it, and a matching band above all but the first. An
   earlier note read row one, applied 22px to the lot, and left the page 577px
   short of live. Kept above the mobile rules below, which override it. */
.dir__row {
  display: grid; align-items: center; gap: 24px;
  grid-template-columns: 150px minmax(0, 1fr);
  padding: 35px 20px;
  text-align: left;
}
.dir__row:first-child { padding-top: 0; }
.dir__row:nth-child(odd) { background: #EAEAEA; }

.dir__thumb { width: 150px; height: 150px; object-fit: cover; }
.dir__thumb--none { display: block; background: #DDD; }

.dir__facts {
  margin: 0; display: grid; gap: 20px;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.dir__field dt {
  color: var(--red); font-size: 28px; font-weight: 400; line-height: 1.3;
  margin-bottom: 6px;
}
/* One step darker than --body. The rows alternate onto #EAEAEA, and #757575
   on that ground is 3.83:1, under the 4.5 these 18px values need. #686868 is
   4.63:1 on the band and 5.57:1 on white, and reads the same either way.
   Lightening the band instead cannot get there: even #F7F7F7 only reaches
   4.30:1, and by then the banding has gone. */
/* @paddyspizzapies.clickandcollect... has nowhere to break, and without this
   it pushed the page to 507px inside a 390px window. minmax(0, 1fr) sizes the
   column correctly; it does not make a single long word wrap. */
.dir__field dd {
  margin: 0; font-size: 18px; line-height: 1.4; color: #686868;
  overflow-wrap: anywhere;
}

/* Anywhere else a long address might appear. Only takes effect when a word
   would otherwise overflow its box. */
.prose p, .prose li, .dir__field dt { overflow-wrap: break-word; }

@media (max-width: 760px) {
  .dir__row, .dir__row:first-child { grid-template-columns: 90px minmax(0, 1fr); gap: 16px; padding: 16px 12px; }
  .dir__thumb { width: 90px; height: 90px; }
  .dir__facts { grid-template-columns: 1fr; gap: 10px; }
  /* Bold as well as smaller. At 28px the red labels count as large text and
     need 3:1, which they have; at 19px regular they would need 4.5:1, and axe
     measured 3.99:1 on white and 3.32:1 on the grey rows. Bold at 19px counts
     as large text again (from 18.66px bold), so the red stays and passes.
     Found in the final accessibility scan, 23 Sep 2026. */
  .dir__field dt { font-size: 19px; font-weight: 700; margin-bottom: 2px; }
}

/* ---------- logo wall ---------- */
/* Five bands of client logos, in the live page's own rows: one row of 6 then
   four rows of 4. Each row is its own grid so the columns stay fixed instead
   of auto-filling and repacking as the viewport changes. Logos are height-
   constrained and centred, so wildly different source crops still sit on a
   common baseline. */
.logowall { display: grid; gap: 46px; }
.logos {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 30px; align-items: center;
}
.logos--6 { grid-template-columns: repeat(6, 1fr); }
.logos--4 { grid-template-columns: repeat(4, 1fr); }
.logos li { display: grid; place-items: center; min-width: 0; }
.logos img {
  max-width: 100%; max-height: 130px; width: auto; height: auto;
  object-fit: contain;
}

/* ---------- full-bleed feature ---------- */
.feature {
  margin-top: 60px;
  min-height: 420px; display: grid; place-items: center;
  background: #333 center/cover no-repeat;
}
.feature__label {
  display: inline-block;
  background: rgba(255, 0, 0, .78); color: #fff; text-decoration: none;
  font-size: clamp(28px, 4vw, 52px); font-weight: 700; line-height: 1.1;
  padding: 22px 44px;
}
a.feature__label:hover { background: rgba(255, 0, 0, .92); }

/* ---------- form ---------- */
/* Measured off the live forms: labels above rounded 20px fields with a light
   border and soft inset, half-width pairs for dates and names, and a red pill
   Submit. Input font is 20px like the live site's. */
.form-wrap { max-width: 800px; text-align: left; }
/* Two-column grid: full rows span both tracks, the date and name pairs sit
   half-width side by side, exactly as the live form lays them out. */
/* minmax(0,1fr) rather than 1fr: a bare 1fr track has an auto minimum, so the
   county <select> — whose min-content width is set by its longest option —
   pushed its track wide enough to scroll the whole page sideways. */
.form { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 0 24px; }
.form__row { min-width: 0; }
.form select { max-width: 100%; }
.form__row { margin-bottom: 18px; display: grid; gap: 8px; grid-column: 1 / -1; }
.form__row--half { grid-column: auto; }
.form__hp, .form__submit, .form input[type=hidden] { grid-column: 1 / -1; }
.form__row label { font-size: 17px; font-weight: 700; color: var(--ink); }
.req { color: var(--red-ui); }
.opt { font-weight: 400; color: var(--body); }
.form input[type='text'], .form input[type='email'], .form input[type='tel'],
.form input[type='date'], .form input[type='search'],
.form select, .form textarea {
  font: 400 20px/1.4 'Raleway', Arial, sans-serif;
  color: var(--ink); background: #fff;
  border: 1px solid #EBEBEB; border-radius: 8px;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, .06);
  padding: 16px 22px; width: 100%;
}
.form ::placeholder { color: #9A9A9A; }
.form input:focus, .form select:focus, .form textarea:focus {
  outline: 3px solid var(--red-ui); outline-offset: 1px;
}
.form__row--consent label { font-weight: 400; }
/* The whole line is the label, so tapping the text ticks the box. The padding
   gives the row a 44px target on a phone without moving anything: the box
   itself stays 20px, which is what it looks like on the live form. */
.consent { display: flex; gap: 10px; align-items: flex-start; font-size: 16px; color: var(--ink);
  padding: 12px 0; cursor: pointer; }
.consent input { width: 20px; height: 20px; margin-top: 2px; flex: none; cursor: pointer; }
/* .form__note had no rule of its own, so it fell back to the browser's default
   paragraph margin and stacked unpredictably with the grid gap, the consent
   row's padding and the submit row's. Astra found it reading the source; I had
   looked at the same screenshot and seen only "too tight". */
.form__note { margin: 0; }
.form__row--consent { row-gap: 12px; margin-bottom: 24px; }
.form__submit { text-align: center; padding-top: 8px; }
.btn--pill { border-radius: 30px; font-size: 20px; padding: 15px 74px; }
/* Honeypot: visually gone, still in the form for bots to fill. Clipped rather
   than pushed off-canvas — a left:-9999px element still counts toward the
   document's scroll width, which is what was making one page report a
   horizontal overflow it did not visibly have. */
.form__hp {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* ---------- map ---------- */
.map { aspect-ratio: 16 / 7; background: #eee; }
.map iframe { width: 100%; height: 100%; border: 0; display: block; }

/* ---------- lightbox ---------- */
.lb {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(0, 0, 0, .92);
  display: grid; place-items: center; padding: 32px;
}
.lb[hidden] { display: none; }
.lb img { max-width: 100%; max-height: 88vh; width: auto; height: auto; }
.lb__close, .lb__prev, .lb__next {
  position: absolute; background: none; border: 0; cursor: pointer;
  color: #fff; font: 700 40px/1 'Raleway', Arial, sans-serif; padding: 14px 20px;
}
.lb__close { top: 8px; right: 12px; }
.lb__prev { left: 6px; top: 50%; transform: translateY(-50%); }
.lb__next { right: 6px; top: 50%; transform: translateY(-50%); }
.lb__count {
  position: absolute; bottom: 14px; left: 0; right: 0;
  color: #fff; font-size: var(--fs-nav); font-weight: 700;
}

/* ---------- footer ---------- */
/* The live footer: roundel | address block | page menu | privacy menu, white
   on red, square bullet markers, a hairline under each menu item. */
.ftr { background: var(--red-ui); color: #fff; font-size: var(--fs-nav); text-align: left; }
.ftr a { color: #fff; text-decoration: none; }
.ftr a:hover { text-decoration: underline; }
.ftr__grid {
  max-width: var(--wrap); margin-inline: auto; padding: 54px 16px 34px;
  display: grid; gap: 40px;
  grid-template-columns: 220px 1.4fr 1fr 1fr;
  align-items: start;
}
.ftr__col--logo img { width: 200px; height: auto; }
.ftr__col h2 { font-size: 25px; font-weight: 700; margin: 0 0 14px; }
.ftr__col h3 { font-size: 18px; font-weight: 700; margin: 18px 0 4px; }
.ftr address { font-style: normal; line-height: 1.6; }
.ftr__lines { margin: 16px 0 0; line-height: 1.7; }
.ftr__social { list-style: none; margin: 18px 0 0; padding: 0; display: flex; gap: 16px; }
.ftr__social a { display: grid; place-items: center; }
.ftr__social svg { width: 34px; height: 34px; }
.ftr__social a:hover { opacity: .8; text-decoration: none; }
.ftr__col--nav ul { list-style: none; margin: 0; padding: 0; }
.ftr__col--nav li {
  border-bottom: 1px solid rgba(255, 255, 255, .25);
  padding: 9px 0 9px 18px; position: relative;
}
.ftr__col--nav li::before {
  content: ''; position: absolute; left: 0; top: 50%; transform: translateY(-50%);
  width: 7px; height: 7px; background: #fff;
}
.ftr__base {
  max-width: var(--wrap); margin-inline: auto;
  border-top: 1px solid rgba(255, 255, 255, .35);
  padding: 20px 16px; text-align: left;
}

/* ---------- back to top ---------- */
.totop {
  position: fixed; right: 20px; bottom: 20px; z-index: 90;
  width: 48px; height: 48px; border-radius: 4px;
  background: #F2F2F2; color: var(--ink);
  display: grid; place-items: center;
  box-shadow: 0 1px 6px rgba(0, 0, 0, .22);
}
.totop:hover { background: #fff; }
.totop[hidden] { display: none; }

/* ---------- search page ---------- */
.search-status { font-size: 22px; }
.search-results { list-style: none; margin: 24px 0 0; padding: 0; }
.search-results li { border-bottom: 1px solid var(--line); padding: 14px 0; }
.search-results a { font-size: 22px; font-weight: 700; }
.search-results span { display: block; color: var(--body); font-size: 15px; margin-top: 2px; }

/* ---------- responsive ---------- */
@media (max-width: 1080px) {
  .hdr__bar { flex-wrap: wrap; }
  .hdr__toggle { display: block; }
  .nav { flex-basis: 100%; display: none; }
  .nav.is-open { display: block; }
  .nav ul { flex-direction: column; justify-content: flex-start; }
  .nav li + li { border-top: 1px solid var(--line); }
  .nav a { padding: 16px 4px; }
}

@media (max-width: 760px) {
  :root { --fs-h1: 34px; --lh-h1: 42px; --fs-copy: 19px; --lh-copy: 31px; }
  .prose { font-size: 17px; line-height: 26px; }
  .prose--md { font-size: 19px; line-height: 28px; }
  .banner { min-height: 240px; }
  .banner h1 { font-size: 34px; }
  .carousel { min-height: 460px; }
  .carousel--band { min-height: 300px; }
  .quotes { justify-self: center; }
  .carousel__arrow { font-size: 30px; padding: 4px 12px 10px; }
  .hdr__logo img { width: 200px; }
  .band { padding-top: 40px; }
  /* footer stays four columns to 767px, as live does */
  .form { grid-template-columns: 1fr; }
  .form__row--half { grid-column: 1 / -1; }
  .btn--pill { width: 100%; padding: 15px 20px; }
}

@media (prefers-reduced-motion: reduce) {
  /* The carousel's reduced-motion case is at the foot of this file, because
     the rules it has to override are written below this block and would
     otherwise win on order. */
  .shot img { transition: none; }
  .shot:hover img { transform: none; }
}

/* Grid and card collapse points. */
@media (max-width: 1080px) {
  .cards { grid-template-columns: repeat(2, 1fr); }
  .grid--gallery { grid-template-columns: repeat(4, 1fr); }
}
@media (max-width: 760px) {
  .grid--product { grid-template-columns: repeat(2, 1fr); }
  .grid--gallery { grid-template-columns: repeat(3, 1fr); }
  .cards { grid-template-columns: 1fr; }
  .card-tile__title { font-size: 32px; }
}

/* ---------- full-bleed photo band ---------- */
/* /for-sale/ opens with one: measured live at 505px tall, 200px vertical
   padding, content centred over the photograph. */
.photoband {
  background: #333 center/cover no-repeat;
  padding: 200px 16px;
}
.photoband__inner { max-width: var(--wrap); margin-inline: auto; text-align: center; }
.photoband__inner > * { margin: 0; }
@media (max-width: 760px) {
  .photoband { padding: 90px 16px; }
}

/* ---------- product split ---------- */
/* The vehicle-page template: prose left, media right (lead photo over a
   four-across strip of square thumbnails), as measured on the live pages. */
.split { display: grid; grid-template-columns: minmax(0, 1fr) 583px; gap: 40px; align-items: start; }
.split__media { display: grid; gap: 14px; }
.split__lead img { width: 100%; height: auto; display: block; }
.grid--thumbs { grid-template-columns: repeat(4, 1fr); gap: 6px; }
@media (max-width: 1080px) {
  .split { grid-template-columns: 1fr; }
  .split__media { max-width: 620px; }
}

/* ---------- contact split ---------- */
/* Red info card left, form right, as on the live contact page. */
.contact-split { display: grid; grid-template-columns: 480px minmax(0, 1fr); gap: 40px; align-items: start; }
.contact-card { background: var(--red-ui); color: #fff; padding: 34px 30px; }
.contact-card h2 { font-size: 34px; font-weight: 400; margin: 0 0 22px; line-height: 1.2; }
.contact-card address { font-style: normal; font-weight: 700; line-height: 1.65; margin: 0 0 20px; padding-left: 50px; }
.contact-card address em { font-style: italic; }
.contact-card__row {
  margin: 0; padding: 14px 0 14px 50px; font-weight: 700;
  border-top: 1px solid rgba(0, 0, 0, .18);
}
.contact-card__row a { color: #fff; text-decoration: none; }
.contact-card__row a:hover { text-decoration: underline; }
.contact-form { text-align: left; }
@media (max-width: 1080px) {
  .contact-split { grid-template-columns: 1fr; }
}

/* ---------- storage photo pair ---------- */
.grid--pair { grid-template-columns: repeat(2, 1fr); gap: 32px; }
.grid--pair .shot img { aspect-ratio: 4 / 3; }
@media (max-width: 760px) { .grid--pair { grid-template-columns: 1fr; } }

/* ---------- privacy tools ---------- */
.ptools { max-width: 900px; text-align: left; }
.ptools__tabs { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 26px; }
.ptools__tabs button {
  font: 700 15px/1 'Raleway', Arial, sans-serif;
  background: var(--red-ui); color: #fff; border: 0; cursor: pointer;
  padding: 13px 18px; border-radius: 4px;
}
.ptools__tabs button[aria-pressed='true'] { background: #B00000; }

/* ---------- icon columns (About) ---------- */
.icols { display: grid; grid-template-columns: repeat(3, 1fr); gap: 44px; text-align: center; }
.icol__icon { height: 160px; width: auto; margin: 0 auto 26px; }
.icol__head { font-size: 32px; font-weight: 400; color: var(--ink); margin: 0 0 15px; }
.icol p { margin-bottom: 14px; }
.grid--three { grid-template-columns: repeat(3, 1fr); gap: 30px; }
.grid--three .shot img { aspect-ratio: 4 / 3; }
/* Superseded: the live theme holds three columns down to 767px. See the
   ABOUT US breakpoint block below. */

/* Live storage copy is centred; the contact form heading is the live red. */
.prose--center { text-align: center; }
/* Live measures 35px on a 40px line at 1440. This was 42px inheriting the
   body's 22px line: larger than live, and on two lines, which it takes
   below about 1100, the lines overlapped. Corrected 23 Sep 2026. */
.contact-form__head { font-size: 35px; line-height: 40px; font-weight: 400; color: var(--red); margin: 0 0 18px; }
.contact-card__office { margin: 0 0 6px; font-size: 15px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; }

@media (max-width: 900px) {
  .logos--6 { grid-template-columns: repeat(3, 1fr); }
  .logos--4 { grid-template-columns: repeat(2, 1fr); }
}


/* ============================================================================
   HOME PAGE FIDELITY PASS
   Every number below was measured off the live site and its counterpart in
   this build, side by side, at 1440x900. Where a value looks arbitrary it is
   because the live theme's value is arbitrary.
   ========================================================================= */

/* ---- masthead --------------------------------------------------------- */
/* Live: content gutter 34.3px, nav first item x=452, last item ends x=1374,
   link padding 13px a side with a 1px rgba(0,0,0,.05) hairline between items,
   links the full 150px height of the bar, current item pure black, and NO
   bottom border on the header. */
/* Live's masthead gutters are asymmetric: the logo starts at x=34.3 and the
   nav's last item ends 66.3px from the right edge. */
.hdr__bar { max-width: none; padding-left: 34px; padding-right: 66px; }
.nav a { padding: 45px 13px; line-height: 60px; }
/* Live's divider sits on the link's inner text span, so it is only 60px tall
   and centred in the 150px bar — not a full-height rule. */
.nav li + li { position: relative; }
.nav li + li::before {
  content: ''; position: absolute; left: 0; top: 50%;
  transform: translateY(-50%);
  width: 1px; height: 60px; background: rgba(0, 0, 0, .05);
}
.nav a[aria-current='page'] { color: #000; }
.hdr__logo { padding: 15px 0; }

/* Live has no visible search control at all: .search_wrapper exists in the DOM
   but computes display:none. Ours was an addition, so it goes. The /search/
   page and its index stay - they cost nothing and the footer can reach them -
   but nothing in the masthead advertises it. */
.hdr__search-toggle, .hdr__search { display: none !important; }

/* ---- the band between the desktop bar and the mobile menu ------------- */
/* Below the width the menu needs on one row, and above the 1080px mobile
   breakpoint, the last items wrapped onto a second line inside the bar (iPad
   landscape at 1180 sits squarely in that band). Live handles the same widths
   by dropping the whole menu onto its own row under the logo, measured at
   1250 and 1280, so that is what this does: the nav takes the full width,
   still right-aligned, on a shorter row than the 150px desktop bar.

   The band ended at 1250 when the menu measured 884px. With the client's
   renamed labels ("About Us", "Second Hand Trucks for Sale") it is seven
   items and 1025px, so it now needs 1400px, measured: one row at 1400, two
   at 1390. Until 23 Sep 2026 the band still stopped at 1250,
   and between there and 1400, which takes in the 1280 and 1366 laptop
   screens, "Gallery" and "Contact" hung on a second row 150px lower.

   Written as (1080px < width < 1400px), like the laptop ranges in the tidy-up
   at the end of the file, rather than min-width 1081 and max-width 1399: a
   window 1080.5px wide, which browser zoom can produce, matched neither side
   of the old pair. Astra, 23 Sep 2026. */
@media (1080px < width < 1400px) {
  .hdr__bar { flex-wrap: wrap; }
  .nav { flex-basis: 100%; }
  .nav a { padding: 20px 13px; }
}
/* On its own row the menu has the window less 100px, and at 1081 to 1139 that
   is narrower than its 1025px, so "Contact" dropped onto a line of its own.
   9px either side of each label brings it to 969px, inside 981 at 1081. */
@media (1080px < width < 1140px) {
  .nav a { padding-inline: 9px; }
}

/* ---- hero carousel ---------------------------------------------------- */
/* Live's hero fills the window below the 150px masthead rather than sitting at
   a fixed height, so it only agreed with our hard 750px at a 900px window. */
.carousel--hero { min-height: calc(100vh - 150px); }

/* Live runs a "clarendon" media filter over every slide; these two values
   reproduce it. */
.carousel--hero .carousel__slide > img,
.carousel--band .carousel__slide > img { filter: contrast(1.2) saturate(1.35); }

/* ---------- slide transition ----------
   Live does not cross-fade. Its slider is configured transition=slideleft with
   masterspeed=600, read off the slide elements themselves rather than guessed:
   the outgoing slide travels left as the incoming one arrives from the right,
   over 600ms. Both sliders on the page use it.

   Done with three classes rather than a scripted transform, so the whole thing
   stays inside a stylesheet and none of it depends on style-src allowing what
   it does not allow. is-on is the slide you can see; is-prev has just left to
   the left; everything else waits off to the right. */
.carousel__slide {
  opacity: 1;
  transform: translateX(100%);
  transition: transform 600ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
  will-change: transform;
}
.carousel__slide.is-on { transform: translateX(0); }
.carousel__slide.is-prev { transform: translateX(-100%); }

/* Going backwards, the pair swap sides so the movement reverses. */
.carousel.is-back .carousel__slide { transform: translateX(-100%); }
.carousel.is-back .carousel__slide.is-on { transform: translateX(0); }
.carousel.is-back .carousel__slide.is-prev { transform: translateX(100%); }

/* A slide being repositioned for the next move must not animate across the
   viewport to get there. */
.carousel__slide.no-anim { transition: none; }

/* Title: Open Sans 800 on a hard, unblurred shadow. */
.carousel__title {
  font-family: 'Open Sans', 'Raleway', Arial, sans-serif;
  font-weight: 800;
  line-height: 80px;
  text-shadow: 1px 2px 0 #000;
}

/* Arrows and dots are hidden until the pointer is over the slider on live.
   Keyboard focus reveals them too, otherwise they would be unreachable. */
.carousel__arrow, .carousel__dots { opacity: 0; transition: opacity 200ms ease; }
.carousel:hover .carousel__arrow,
.carousel:hover .carousel__dots,
.carousel:focus-within .carousel__arrow,
.carousel:focus-within .carousel__dots { opacity: 1; }

/* The pause button is the exception: always visible, because it is the way
   to stop a slideshow for somebody who never hovers (WCAG 2.2.2). A dark disc
   in the bottom corner, clear of every hero layer at every width (measured
   23 Sep 2026), 32px so it is an easy target. Over a photograph the site's red
   focus ring can vanish into the red truck, so a white ring sits inside it. */
.carousel__play {
  position: absolute; right: 12px; bottom: 12px; z-index: 4;
  width: 32px; height: 32px; padding: 0; border: 0; border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(0, 0, 0, .55); color: #fff; cursor: pointer;
}
.carousel__play[hidden] { display: none; }
.carousel__play:hover { background: rgba(0, 0, 0, .75); }
.carousel__play:focus-visible { outline-offset: 2px; box-shadow: 0 0 0 2px #fff; }
.carousel__play svg { width: 14px; height: 14px; fill: currentColor; }
.carousel__play .carousel__icon-play,
.carousel__play.is-paused .carousel__icon-pause { display: none; }
.carousel__play.is-paused .carousel__icon-play { display: block; }

/* Live's arrows: 100x100 circular, no fill at rest OR on hover, inset 20px. */
.carousel__arrow {
  width: 100px; height: 100px; padding: 0;
  border-radius: 50%; background: none;
  font: 400 40px/1 'Open Sans', Arial, sans-serif;
  display: grid; place-items: center;
}
.carousel__arrow:hover { background: none; }
.carousel__arrow--prev { left: 20px; }
.carousel__arrow--next { right: 20px; }

/* Dots: a small core inside a thick ring, inverted when active, drawn exactly
   as live draws them (13px). The button around each is 24px, and the pitch
   24px rather than live's 18px, because a 13px target 18px from the next one
   fails WCAG 2.5.8 (target size, AA): a thumb aimed at one dot can land on
   the next. Measured in the final pre-launch sweep, 23 Sep 2026. The ring is a
   pseudo-element so the tap area can grow without the dot changing. */
/* The row is a full-width strip, only there to centre the dots, and it sits
   over the foot of the slideshow even while invisible. It took the clicks
   meant for the pause button in the corner: a real pointer test caught it,
   where a scripted click had gone straight through (23 Sep 2026). So the strip
   lets clicks pass and only the dots themselves take them. */
.carousel__dots { gap: 0; bottom: 4px; pointer-events: none; }
.carousel__dots button {
  position: relative; width: 24px; height: 24px; padding: 0;
  border: 0; border-radius: 50%; background: none;
  pointer-events: auto;
}
.carousel__dots button::before {
  content: ''; position: absolute; left: 50%; top: 50%;
  width: 3px; height: 3px; margin: -6.5px 0 0 -6.5px; border-radius: 50%;
  background: #fff; border: 5px solid #000; box-sizing: content-box;
}
/* The base rule far above fills the active button white. With the button now
   24px, that painted a white disc over the ring; the fill belongs to the ring. */
.carousel__dots button[aria-current='true'] { background: none; }
.carousel__dots button[aria-current='true']::before { background: #000; border-color: #fff; }

/* ---- hero fixtures ---------------------------------------------------- */


/* ---- welcome copy ----------------------------------------------------- */
/* Live block rhythm: 95px below the hero, 15px between blocks, and 110px of
   air before the logo-intro line and again before the wall itself. */
.band--welcome { padding-top: 95px; }
.prose--lg > p, .prose--lg > h1, .prose--lg > h2, .prose--lg > h3 { margin-bottom: 15px; }
.prose--lg { color: #7A7A7A; }
.prose--lg [data-logo-intro] { margin-top: 110px; }

/* ---- logo wall -------------------------------------------------------- */
/* Live's wall is 1457px tall against our 651: the cells are far bigger (row 1
   on a 204.3px pitch, rows 2-5 on 306.4px) and the logos are not squeezed to a
   130px cap - Logo-EDO alone renders 233x300. Rows sit 70px apart with 110px
   above the first. */
/* Live's band measures 1457px top to bottom; the rows carry their own
   vertical padding, which reads as an 83px gap between logo rows. */
.logowall { gap: 83px; padding-top: 80px; }
.logos { gap: 30px; }
.logos img { max-height: 300px; }

/* ---- closing band ----------------------------------------------------- */
/* No arrows and no dots on live at all, a rounded orange-red pill in Open Sans
   800 at 60px, and 35px of air above rather than 60. */
.carousel--band { margin-top: 35px; }
.carousel--band .carousel__arrow, .carousel--band .carousel__dots { display: none; }
.carousel__label {
  font-family: 'Open Sans', 'Raleway', Arial, sans-serif;
  font-size: 60px; font-weight: 800; line-height: 85px;
  background: rgba(255, 58, 45, .7);
  border-radius: 30px; padding: 30px 60px;
  text-shadow: 1px 2px 0 #000;
}
.carousel__label:hover { background: rgba(255, 58, 45, .85); }

/* ---- footer ----------------------------------------------------------- */
/* Four equal columns on a 24px gutter, content starting 30px in, a flat 22px
   baseline pitch through the address and contact lines, a 150px logo, faint
   hairlines, and a full-bleed copyright bar. */
.ftr__grid {
  grid-template-columns: repeat(4, 1fr); gap: 24px;
  padding: 30px 16px 58px;   /* live's widgets block measures 416px tall */
}
.ftr__col--logo img { width: 150px; }
.ftr__col h2 { font-size: 24px; line-height: 28px; margin: 0 0 15px; }
.ftr__col h3 { font-size: 18px; line-height: 22px; margin: 15px 0 0; }
.ftr address { line-height: 22px; }
.ftr__lines { line-height: 22px; margin-top: 15px; }
.ftr__social { gap: 4px; margin-top: 15px; }
.ftr__social svg { width: 48px; height: 48px; }
.ftr__col--nav li { border-bottom-color: rgba(255, 255, 255, .08); padding-left: 18px; }
.ftr__col--nav li::before { width: 6px; height: 6px; }

/* Live's copyright bar runs the full page width with a much fainter rule, and
   the back-to-top control lives inside it as a 43px square rather than
   floating over the page. Note the .ftr prefix on the control's own colour:
   once it moved inside the footer, `.ftr a { color: #fff }` out-specified a
   bare `.totop` and painted the chevron white on a white tile. */
.ftr__base {
  max-width: none; margin: 0; padding: 30px 0 0;
  border-top: 1px solid rgba(255, 255, 255, .1);
  min-height: 94px;
  position: relative;
}
.ftr__base > span { display: block; max-width: 1220px; margin-inline: auto; padding-inline: 16px; }
.ftr .totop {
  position: absolute; right: 110px; bottom: 26px; top: auto;
  width: 43px; height: 43px; border-radius: 0;
  background: #fff; color: var(--ink);
  box-shadow: none;
}
.ftr .totop:hover { background: #F2F2F2; }

@media (max-width: 1080px) {
  /* footer stays four columns to 767px, as live does */
  .totop { right: 20px; }
  .carousel--hero { min-height: 520px; }
}

/* ---- hero slide-one fixtures ------------------------------------------ */
/* Every offset here is measured off the live slider and is relative to the
   carousel box, not to a sibling — the layers are independently positioned on
   live, so tying them to each other made them move as a group when any one of
   them was nudged.

   Live, at 1440x900:
     stars     five 82px glyphs on an 82.75px pitch, first at x=139
     roundel   337.5px square at x=148, y=226
     cards     left edge x=606, shrink-wrapped to their own text, both sharing
               a vertical centre at y=88 so the taller one grows both ways
     quote     a large white glyph outside the card's top-left corner        */
.hero-fixtures {
  position: absolute; inset: 0;
  max-width: none; margin: 0;
  pointer-events: none;
}

.hero-fixtures__roundel {
  position: absolute; left: 148px; top: 226px;
  width: 337.5px; height: auto; transform: none;
}

/* ---------- the roundel's entrance ----------
   Read off the live layer rather than guessed. Its one frame is:

     {"delay":10,"speed":2500,"from":"x:left;rY:360deg;opacity:0;",
      "to":"o:1;","ease":"Power4.easeOut"}

   So it arrives from off the left edge while turning a full circle about its
   vertical axis, over two and a half seconds, slowing hard at the end.
   Power4.easeOut is GSAP's quintic ease-out, which cubic-bezier(.23,1,.32,1)
   follows closely.

   It travels 486px, which is its own 337.5px width plus the 148px it sits in
   from the left, so it starts exactly off the edge rather than an arbitrary
   distance away.

   Driven by .is-on rather than by script: the fixtures sit inside the first
   slide, so removing and restoring that class cancels and restarts the
   animation on its own. That is what makes it play again each time the slide
   comes back round, which is what live does. Nothing here needs JavaScript and
   nothing needs a scripted style, so the CSP has no opinion about it. */
.hero-fixtures { perspective: 1200px; }

@keyframes roundel-in {
  from { opacity: 0; transform: translateX(-486px) rotateY(360deg); }
  to   { opacity: 1; transform: translateX(0) rotateY(0deg); }
}

.carousel__slide.is-on .hero-fixtures__roundel {
  animation: roundel-in 2500ms cubic-bezier(0.23, 1, 0.32, 1) 10ms both;
}

.hero-fixtures .stars {
  position: absolute; left: 139px; top: 55px;
  margin: 0; display: flex; gap: 0;
}
.hero-fixtures .stars svg { width: 82px; height: 82px; }

.hero-fixtures__quotes {
  position: absolute; left: 606px; top: 88px;
  width: auto; right: auto; transform: none;
}
.hero-fixtures .quote {
  position: absolute; left: 0; top: 0;
  transform: translateY(-50%);
  width: max-content; max-width: 690px;
  margin: 0; padding: 0;
  opacity: 0; transition: opacity 600ms ease;
}
.hero-fixtures .quote.is-on { opacity: 1; }
.hero-fixtures .quote blockquote {
  position: relative;
  font-family: 'Open Sans', 'Raleway', Arial, sans-serif;
  font-size: 17px; line-height: 19px; font-weight: 600;
  color: #fff; text-align: left;
  padding: 9px 14px; border-radius: 6px;
  background: rgba(255, 58, 45, .7);
}
/* The quotation mark live paints outside the card's top-left corner.

   Live draws it with a FontAwesome icon at 36px white, in a 33x36 box sitting
   22px left and 17px above the card. This uses the site's own Open Sans
   instead: pulling in an icon font for one glyph would mean a second
   typeface, a third-party file the CSP would have to allow, and an
   attribution obligation, all for a quotation mark.

   The size is not live's 36px, because the two glyphs are not the same shape.
   FontAwesome's icon fills its box; Open Sans's quotation mark sits in the
   upper third of the em. It is sized to match what is painted rather than
   what is declared.

   The shadow is live's exactly, 0 0 10px black. Without it a white mark on a
   pale sky is invisible, which is what this was: the rule had been here all
   along, painting something nobody could see. */
.hero-fixtures .quote blockquote::before {
  content: '\201C';
  position: absolute; left: -25px; top: -30px;
  font: 700 87px/1 'Open Sans', Georgia, serif;
  color: #fff;
  text-shadow: 0 0 10px #000;
}

/* The slide-one title clears the roundel on live rather than centring in the
   full frame. Live's text runs from x=567 to x=1263 at 1440, centred on 915,
   so the left padding is 406px: with the 16px on the right, (406 + 1424) / 2
   is 915. It was 200px until 23 Sep 2026, which centred the title 95px
   further left and ran its first letter under the roundel at every desktop
   width, 1440 included. Below 1440 it narrows with the window (28.2vw is
   406px at 1440), as the fixtures do further down. */
.carousel--hero .carousel__title--offset { padding-left: min(406px, 28.2vw); }

@media (max-width: 1080px) {
  .hero-fixtures__roundel { left: 20px; top: 50%; transform: translateY(-50%); width: 200px; }
  .hero-fixtures .stars { left: 20px; top: 24px; }
  .hero-fixtures .stars svg { width: 44px; height: 44px; }
  .hero-fixtures__quotes { left: auto; right: 20px; width: min(58%, 420px); }
  .hero-fixtures .quote { width: 100%; }
  .carousel--hero .carousel__title--offset { padding-left: 0; }
}


/* ============================================================================
   ABOUT US FIDELITY PASS
   Measured live-vs-build at 1440x900, 768x1024 and 390x844.
   ========================================================================= */

/* ---- page-title banner ------------------------------------------------ */
/* Live fixes the banner photograph to the viewport, so it parallaxes and is
   cropped at a different scale than a scrolling background. */
.banner { background-attachment: fixed; }
.banner h1 { line-height: 65px; }
.crumb { margin-top: 10px; line-height: 30px; }
.crumb svg { width: 7px; height: 11px; fill: currentColor; }

/* ---- intro heading ---------------------------------------------------- */
/* Live: 37.5px below the banner, 60px of air beneath, with an inward-pointing
   triangle either side of the text. */
.band--intro { padding-top: 37.5px; padding-bottom: 60px; }
.fancy {
  /* 22px is the measured distance from the painted edge of live's triangle to
     the start of its text. Live gets there with a wide side bearing inside the
     icon font's 59px box; here the box is the mark itself, so the same air has
     to come from the flex gap instead. */
  display: flex; align-items: center; justify-content: center; gap: 22px;
  margin: 0;
}
.fancy__arrow { flex: none; fill: var(--red); }

/* ---- icon columns ----------------------------------------------------- */
/* Icons are natively 180x180 and live renders them at that size; the build was
   scaling them down to 160. Headings run on a 35px line box in pure black. */
.icol__icon { width: 180px; height: 180px; margin-bottom: 20px; }
.icol__head {
  font-size: 32px; line-height: 35px; font-weight: 400;
  color: #000; margin: 0 0 12px;
}
.icols { gap: 45px; padding-bottom: 52.5px; }

/* ---- photographs ------------------------------------------------------ */
/* Live shows these at their natural aspect ratio, unlinked, with no hover
   effect. The build was cropping every one to a 4:3 box and adding a zoom. */
.grid--three { gap: 35px 30px; }
.shot-plain {
  display: block; width: 100%; height: auto;
  object-fit: unset; aspect-ratio: auto;
}

/* ---- responsive breakpoints ------------------------------------------- */
/* The live theme holds its desktop layout down to 767px: at 768 it still has
   three icon columns, a three-across photo grid and a four-column footer. The
   build was collapsing all three at 900 and 1080, which at tablet width made
   the page 2.4x taller than live. */
@media (max-width: 767px) {
  /* 120px of empty screen is a lot on a phone. */
  :root { --page-end-gap: 80px; }
  .icols { grid-template-columns: 1fr; }
  .grid--three { grid-template-columns: 1fr; }
  .ftr__grid { grid-template-columns: 1fr; }
}

/* ---- About Us: photograph rows, second pass ---------------------------- */
/* Live crops these to a uniform height within each row rather than letting
   each keep its own aspect - a mixed-aspect row reads as ragged against live's
   even one. Live's two rows measure 284px and 296px tall at 379px wide; a
   single 4:3 box sits between them and the 12px difference is not visible.
   They stay unlinked and hover-free, which the live page does too. */
.grid--three .shot-plain {
  aspect-ratio: 4 / 3; object-fit: cover;
  width: 100%; height: auto;
}
/* Live leaves 35px between the last photo row and the footer, not 60. */
.band--photos { padding-top: 0; padding-bottom: 35px; }


/* ============================================================================
   CATEGORY PAGE FIDELITY PASS
   Measured live-vs-build at 1440x900, 768x1024 and 390x844.
   ========================================================================= */

/* ---- page-title banner ------------------------------------------------ */
/* Live's banner is a 370px band whose 105px text stack starts 130px from the
   top - it is not vertically centred, and the inner column runs the full
   content width so the h1 is a centred 1034px box rather than shrink-to-fit. */
/* place-items: center on .banner makes its one grid item size to max-content,
   so a long title set at 60px stretched the box past the viewport: "Vintage
   Volkswagens" put the page at 410px inside a 390px window. Stretching the
   item instead lets the title wrap onto two lines, which is what it should
   have done. The inner box still centres its own text. */
.banner { background-repeat: no-repeat; align-content: start; justify-items: stretch; }
.banner__inner { min-width: 0; max-width: min(var(--wrap), 100%); }
.banner h1 { max-width: 100%; overflow-wrap: break-word; }
.banner__inner {
  width: 100%; max-width: var(--wrap); margin-inline: auto;
  padding: 130px 16px 0; text-align: center;
}
.banner h1 { width: 100%; text-align: center; }
/* Live paints a 3px shadow strip under the band. */
.banner::after {
  content: ''; position: absolute; left: 0; bottom: -3px;
  width: 100%; height: 3px; z-index: 1;
  background: linear-gradient(to bottom, rgba(0, 0, 0, .11), rgba(0, 0, 0, 0));
}
/* Live's separator sits in a 25px box with ~10px either side. */
.crumb { gap: 10px; }
.crumb svg { width: 7px; height: 11px; margin-inline: 9px; }

/* ---- section headings ------------------------------------------------- */
/* Live sets these on a 55px line box with 30px of padding beneath and a 15px
   margin, so each heading block is 85px tall. The build had them inheriting a
   22px line box, collapsing the block to 22px and pulling every grid up. */
.cards-head {
  line-height: 55px;
  margin: 0 0 15px;
  padding: 0 0 30px;
}

/* ---- card grid -------------------------------------------------------- */
/* Live: 378.5px tiles on a 408.5px step (29.5px gap); 100px from a heading to
   the grid beneath it and ~80px from a grid to the next heading. */
.cards {
  gap: 29.5px;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin: 55px 0 80px;
}
/* Live insets the title 30px from each tile edge and advances lines by 40px,
   so long titles break where live breaks them. */
.card-tile__title {
  padding: 9.5px 40px;
  line-height: 40px;
  /* live stacks the same shadow twice, giving a denser halo over busy photos */
  text-shadow: 1px 1px 10px rgba(0, 0, 0, 1), 1px 1px 10px rgba(0, 0, 0, 1);
}
.card-tile__btn {
  display: inline-block;
  border: 1px solid transparent;
  line-height: normal;
}
/* Live has no card-wide hover: the image never zooms, and only the button
   itself responds. */
.card-tile:hover img { transform: none; }
.card-tile:hover .card-tile__btn { background: #F7F7F7; }
.card-tile__btn:hover { background: #fff; }

/* ---- intro band ------------------------------------------------------- */
/* Live leaves 70px between the banner and the intro paragraph. */
.band--catintro { padding-top: 70px; }

/* ---- back-to-top ------------------------------------------------------ */
.ftr .totop { background: #F7F7F7; }

/* ---- responsive ------------------------------------------------------- */
/* The live theme keeps a three-across card grid down to 767px; the build was
   dropping to two at 1080, which alone made the page 1,791px taller than live
   at tablet width. It also never shrinks the masthead or the page title, and
   its content gutters are far wider than the build's 16px. */
@media (max-width: 1080px) {
  .cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 767px) {
  .cards { grid-template-columns: 1fr; }
}
@media (max-width: 900px) {
  /* live keeps the masthead at 150px and the title at 60px/65px at every
     viewport - no mobile reduction at all */
  .hdr__bar { padding-left: 20px; padding-right: 20px; }
  .banner h1 { font-size: 60px; line-height: 65px; }
  .banner__inner { padding-top: 80px; padding-bottom: 80px; }
  .wrap { padding-inline: 33px; }
}

/* Live runs the three category blocks inside ONE continuous section, so the
   spacing between a grid and the next heading comes entirely from the grid's
   own margin. The build wraps each in its own band; those bands must therefore
   contribute no padding of their own, or every gap is 30px too generous. */
.band--cards { padding-top: 0; padding-bottom: 0; }

/* ---- Catering Hire: paired feature lists --------------------------------
   Live lays these as two 379px columns inset from the 1196px content width,
   starting at x=321 and x=730 - i.e. a centred pair with a 30px gutter. */
.twocol {
  display: grid; gap: 30px;
  /* minmax(0, ...) rather than a bare 379px: two fixed columns and the gutter
     need 788px, so between 768 and about 820 the page scrolled sideways. At
     desktop widths they are exactly 379px, as before. */
  grid-template-columns: repeat(2, minmax(0, 379px));
  justify-content: center;
  text-align: left;
}
/* The two column heads are h3 and h4 after level normalisation, and their
   differing UA top margins pushed the second column 11px lower than the
   first; live has both flush. */
.twocol__col > * { margin: 0 0 15px; }
.twocol__col > :first-child { margin-top: 0; }
@media (max-width: 767px) {
  .twocol { grid-template-columns: 1fr; }
}

/* Catering Hire's second photo row is square on live (379x379) where the first
   is 379x284. */
.grid--three-sq .shot img { aspect-ratio: 1 / 1; }

/* ---- Catering Hire section rhythm ---------------------------------------
   Live builds this page from six rows, each carrying its own generous
   padding (the row boxes measure 550/945/1428/2345 against content that
   starts 60px inside them). The build's default 60/30px bands ran the whole
   page ~540px tight, so every landmark drifted progressively earlier. These
   are scoped to the page rather than applied globally, because the pages
   already matching live use the default rhythm. */
.page-catering-hire .band { padding-top: 90px; padding-bottom: 55px; }
.page-catering-hire .band--tight { padding-top: 55px; padding-bottom: 55px; }

/* Intro line under a category section heading (Vintage Builds). */
.cards-note { margin: -5px 0 30px; text-align: center; font-size: 18px; color: var(--body); }

/* Client-supplied lead-time note under the Custom Built intro. Centred to sit
   with the intro paragraph above it, which the page centres. */
.lead-note {
  margin: 18px 0 0; text-align: center;
  font-size: 22px; font-weight: 700; color: var(--ink);
}

/* ============================================================================
   ENQUIRY FORM STATES
   The live site had none of these: its form reloaded the page and printed one
   notice at the top. These belong to the rebuild, so they follow the site's
   own type and colour rather than copying anything.
   --red-ui is used rather than --red because this is small text, and #FF0000
   on white is 4.00:1, under the 4.5:1 that body-size text needs.
   ========================================================================= */

.form__err {
  grid-column: 1 / -1;
  margin: 0;
  font-size: 15px;
  line-height: 22px;
  font-weight: 700;
  color: var(--red-ui);
}

.form [aria-invalid='true'] {
  border-color: var(--red-ui);
  /* colour alone never carries the message: the border thickens too, for
     anyone who cannot separate red from grey */
  border-width: 2px;
}

.form__status {
  grid-column: 1 / -1;
  margin: 0 0 18px;
  font-size: 17px;
  line-height: 26px;
}
.form__status:empty { display: none; }
.form__status--bad {
  padding: 14px 18px;
  border-left: 4px solid var(--red-ui);
  background: #F7F7F7;
  color: var(--ink);
}

/* What replaces the form once it has sent. */
.form__done {
  padding: 34px 30px;
  background: #F7F7F7;
  border-top: 4px solid var(--red);
  text-align: left;
}
.form__done p { margin: 0 0 10px; font-size: 17px; line-height: 28px; }
.form__done p:last-child { margin-bottom: 0; }
.form__done-head {
  font-family: 'Lobster', cursive;
  font-size: 30px;
  line-height: 40px;
  color: var(--ink);
}

/* The endpoint's own no-JavaScript reply page borrows the site stylesheet. */
.wrap--narrow { max-width: 720px; }

/* ---------- narrow screens: the page title ----------
   After the rule it corrects, on purpose. An earlier rule pins the title at 60px for
   max-width 900px, which is right down to about 600px and wrong below it: the
   longest title on the site, "Vintage Volkswagens", is physically wider than a
   390px screen at 60px, and letting it break produced "Volkswagen" with a lone
   "s" underneath. Live has the same rule and the same overflow. Scaling the
   type is the smaller departure of the two, and it only takes effect on
   screens narrower than 600px. */
@media (max-width: 600px) {
  .banner h1 { font-size: clamp(34px, 10vw, 60px); line-height: 1.08; }
}

/* ============================================================================
   PHONE, TABLET AND LAPTOP TIDY-UP, 23 Sep 2026
   The pass promised to the client in writing before launch, from Astra's
   phone review, measured on the pages rather than judged by eye, and then
   swept across every route from 360 to 1920 wide. At 1440 and wider, the
   width the desktop was matched to live at, only line breaks change (the
   balance rules below), plus the two corrections made to match live more
   closely: the slide-one title's position and the contact heading. Where
   live is no guide (it keeps its desktop type at every width, so its own
   phone pages overlap and strand words too), this follows the build's own
   phone rhythm instead.
   ========================================================================= */

/* ---- home hero, 1081 to 1439px ---------------------------------------------
   The desktop layers are placed in pixels measured off live at 1440 and did
   not shrink with the window. On a 1280 laptop the testimonial card, 606px in
   and up to 690px wide, ran off the right edge and was cut off mid-sentence.
   Live's slider scales every layer with the window, and so does this, in
   steps: zoom scales the layers' positions, sizes and type together, the way
   the slider does. Each step is the largest that keeps the card inside the
   frame and 40px or more between the roundel and the title at the narrowest
   width it covers. */
@media (1080px < width < 1440px) { .hero-fixtures { zoom: .94; } }
@media (1080px < width < 1360px) { .hero-fixtures { zoom: .87; } }
@media (1080px < width < 1260px) { .hero-fixtures { zoom: .8; } }
@media (1080px < width < 1160px) { .hero-fixtures { zoom: .75; } }

/* ---- home hero, up to 1080px -----------------------------------------------
   The desktop arrangement is measured off live at 1440 and cannot shrink:
   below 1080 the longer testimonial, centred on a point 88px down, grew 16px
   past the top of the slider on a tablet and 120px past it on a phone, taking
   its quotation mark with it, while the stars, roundel and title piled into
   one another. Every layer now has its own corner. The testimonial hangs from
   a fixed top and grows downward, so its length can never push it out of the
   frame; the roundel sits bottom left and the title bottom right of it.

   The roundel is anchored by bottom and left only, with no transform of its
   own: its entrance animation fills forward and replaces the transform
   anyway, which is why the old translateY(-50%) was never what anybody saw. */
@media (max-width: 1080px) {
  .hero-fixtures__quotes { top: 34px; }
  .hero-fixtures .quote { transform: none; }
  .hero-fixtures__roundel { top: auto; bottom: 20px; transform: none; }
  .carousel--hero .carousel__title--offset {
    place-items: end center; text-align: center;
    padding: 0 20px 44px 240px;
  }
}

@media (max-width: 767px) {
  .hero-fixtures .stars { left: 16px; top: 16px; }
  .hero-fixtures .stars svg { width: 30px; height: 30px; }
  .hero-fixtures__quotes { left: 24px; right: 16px; top: 78px; width: auto; }
  .hero-fixtures .quote { width: 100%; max-width: none; }
  .hero-fixtures .quote blockquote { font-size: 15px; line-height: 20px; padding: 9px 12px; }
  /* Scaled with the card, and kept clear of the stars above it. */
  .hero-fixtures .quote blockquote::before { left: -18px; top: -26px; font-size: 62px; }
  .hero-fixtures__roundel { left: 16px; bottom: 36px; width: 104px; }
  /* 56px from the foot rather than 44: the pause button sits in the bottom
     right corner, and at 390 the end of "Trucks" came within 2px of it. */
  .carousel--hero .carousel__title--offset {
    font-size: 34px; line-height: 1.05;
    padding: 0 16px 56px 132px;
  }
}

/* ---- the For Sale banner title below 1440px ------------------------------
   72px on a 55px line, as live sets it: harmless on one line, overlapping on
   two. "Second Hand Trucks for Sale" is the client's longer name for live's
   "Food Trucks for Sale"; it fits on one line at 1440, where live's spacing
   is kept, and wraps below that. (The contact heading had the same fault; it
   now takes live's own 35/40 at every width, at its rule further up.) */
@media (width < 1440px) {
  main .wrap .t17 { line-height: 1.1; }
}

/* ---- phone type ---------------------------------------------------------
   The harvested styles in content.css are live's desktop sizes, applied at
   every width. On a phone that set a 72px heading on a 55px line, so its
   lines overlapped, ran the home intro at 25px on a 65px line, and made the
   page headings larger than the page title. These keep each style's colour,
   face and weight and bring the size into a phone's proportions. The main
   prefix is there because content.css loads after this file. */
@media (max-width: 767px) {
  main .wrap .t17 { font-size: 48px; line-height: 54px; }
  main .wrap .t18 { font-size: 40px; line-height: 46px; }
  main .wrap .t2 { font-size: 38px; line-height: 44px; }
  main .wrap .t3 { font-size: 24px; line-height: 31px; }
  main .wrap .t16 { font-size: 32px; line-height: 38px; }
  /* About's "3 steps take you through our process:", which shares its line
     with the red arrow: five lines at 42px on a 360px phone. */
  main .wrap .t0 { font-size: 32px; line-height: 38px; }
  main .wrap .t21 { font-size: 38px; line-height: 44px; }
  main .wrap :is(.t22, .t23) { font-size: 20px; line-height: 31px; }
  /* At 25px this grey passed as large text; at 20px it is ordinary text and
     needs 4.5:1. #7a7a7a on white is 4.29:1. #757575, the grey live already
     uses for the paragraph beneath it, is 4.61:1. Astra, 23 Sep 2026. */
  main .wrap .t22 { color: #757575; }
  /* 42px on a 22px line: one line on a desktop, two overlapping ones here. */
  .contact-form__head { font-size: 32px; line-height: 38px; }

  /* A phone crops the banner photographs to their brightest part, and the
     white title and breadcrumb faded into the sky on Contact and the H Van
     page. Live's hard 1px shadow stays, with a soft dark halo under it.
     Astra, 23 Sep 2026. */
  .banner h1, .crumb { text-shadow: 1px 1px 0 #000, 0 0 6px #000; }
}

/* No heading ends on one stranded word, at any width: "Welcome to Retro Food
   / Trucks" at 768, "(New / 2024)" in a banner at 1440, and on For Sale a
   lone "!" ("adventurous !") on the last line at 1440. balance evens out the
   lines of a heading, and that includes the heading-styled paragraphs on For
   Sale and Catering Hire: pretty was tried on those first and left
   "branding." alone on a line, because Chrome only counts a last line under a
   third of the width as an orphan. pretty stays for ordinary paragraphs.
   Neither changes how many lines a block takes, only which words share them,
   so the desktop keeps the heights measured against live; and a browser
   without either wraps exactly as before. */
main :is(h1, h2, h3, h4), .banner h1 { text-wrap: balance; }
main :is(p, li) { text-wrap: pretty; }

/* ---- breadcrumb on a phone -----------------------------------------------
   A long page name wrapped under "Home", leaving the separator hanging at the
   end of a line on its own. It now stays on one line and the page name gives
   way with an ellipsis; the name is the page title printed in full directly
   above it, and the whole of it is still read out to a screen reader. */
@media (max-width: 767px) {
  .crumb { flex-wrap: nowrap; }
  .crumb > a, .crumb > svg { flex: none; }
  .crumb > [aria-current] { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}

/* ---- Catering Hire rhythm on a phone ------------------------------------
   The page's own section padding (90px above, 55px below) is live's desktop
   rhythm and it is scoped by page, so it outranked the phone spacing every
   other page uses. Two sections meeting left about 145px of empty screen, and
   200px between "Ready to book" and the first supplier. On a phone the page
   now follows the same rhythm as the rest of the site. */
@media (max-width: 767px) {
  .page-catering-hire .band,
  .page-catering-hire .band--tight { padding-top: 40px; padding-bottom: 0; }
  .page-catering-hire .band--cards { padding-top: 0; }
}

/* ---- footer bar on a phone -----------------------------------------------
   The back-to-top square is placed 110px in from the right for a desktop's
   width, which on a phone put it on top of the copyright line. Live's own
   phone footer stacks them, the square centred 15px above a centred line,
   and so does this. */
@media (max-width: 767px) {
  .ftr__base {
    display: flex; flex-direction: column; align-items: center; gap: 15px;
    min-height: 0; padding: 26px 16px; text-align: center;
  }
  .ftr__base > span { padding-inline: 0; }
  .ftr .totop { position: static; order: -1; }
}

/* ---------- reduced motion: the carousel ----------
   After every carousel rule on purpose; only the unrelated cookie banner
   follows it. The slide and star rules above are written
   after the main prefers-reduced-motion block, so overriding them there did
   nothing at all: the transition stayed at 600ms for somebody who had asked
   for no movement. Found by checking the computed style with the setting on
   rather than by trusting the rule was in the file.

   With motion off, slides cross-fade in place and the stars are simply
   present. Autoplay does not start either, which the script already handles. */
@media (prefers-reduced-motion: reduce) {
  .carousel__slide,
  .carousel.is-back .carousel__slide {
    transform: translateX(0);
    opacity: 0;
    transition: opacity 200ms ease;
  }
  .carousel__slide.is-on,
  .carousel.is-back .carousel__slide.is-on { transform: translateX(0); opacity: 1; }
  .carousel__slide.is-prev,
  .carousel.is-back .carousel__slide.is-prev { transform: translateX(0); opacity: 0; }
  .carousel--hero .stars svg { animation: none; opacity: 1; transform: none; }
  /* The roundel is simply there, rather than flying in and spinning. */
  .carousel__slide.is-on .hero-fixtures__roundel {
    animation: none; opacity: 1; transform: none;
  }
}

/* ---------- cookie consent ---------- */
/* Dark rather than red: the footer is already full-bleed red, and a red bar
   above it read as part of the furniture rather than as a question. Both
   answers are the same size and sit together, which is what makes refusing as
   easy as accepting. */
.cookies {
  position: fixed; inset-inline: 0; bottom: 0; z-index: 60;
  background: #1A1A1A; color: #fff;
  box-shadow: 0 -2px 20px rgba(0, 0, 0, .3);
}
.cookies[hidden] { display: none; }
.cookies__inner {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 18px; padding-block: 18px;
}
.cookies__text { margin: 0; font-size: 16px; line-height: 24px; max-width: 70ch; color: #fff; }
.cookies__text a { color: #fff; }
.cookies__actions { display: flex; gap: 10px; flex-shrink: 0; }
/* Both answers are styled identically, on Astra's reading of the DPC guidance:
   a red fill on Allow beside an outline on No thanks makes one answer the
   suggested one, and consent that is nudged is not freely given. Same size,
   same colour, same hover, side by side. */
.cookies__btn {
  font: inherit; font-size: 16px; line-height: 1; cursor: pointer;
  padding: 14px 24px; border-radius: 5px;
  border: 2px solid #fff; background: transparent; color: #fff;
}
.cookies__btn:hover { background: #fff; color: #1A1A1A; }
/* While the bar is up it sits over the foot of the page, so the page is given
   the room back. The class goes on when the banner is shown and comes off with
   it, so a visitor who has already answered gets no stray space. */
.has-cookies { padding-bottom: 120px; }
/* The same room for keyboard focus. The bar is fixed over the foot of the
   window, and on a desktop the hero fills the window, so its pause button sat
   underneath it: a control can take focus while entirely hidden, which WCAG
   2.4.11 rules out. With scroll padding, anything that takes focus down there
   is scrolled up clear of the bar first. The html element is the scroller,
   and the class sits on body, hence :has(). 23 Sep 2026. */
html:has(body.has-cookies) { scroll-padding-bottom: 140px; }
@media (max-width: 767px) {
  .cookies__inner { flex-direction: column; align-items: stretch; gap: 14px; }
  .cookies__actions { width: 100%; }
  .cookies__btn { flex: 1 1 0; }
  .has-cookies { padding-bottom: 210px; }
  html:has(body.has-cookies) { scroll-padding-bottom: 230px; }
}
/* Two equal columns, so the answers are the same width whatever their
   wording, as well as the same colour and size. Astra, 23 Sep 2026. */
.cookies__actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* Cookie settings, which reopens the banner so an answer can be changed as
   easily as it was given. It acts rather than navigates, so it is a button,
   dressed as the text around it: a footer link in the footer list, underlined
   on the cookies page. Only rendered, and only unhidden by consent.js, while
   analytics is switched on. 24px tall at least, the WCAG 2.5.8 target size.
   The site's red focus ring disappears on the red footer, so here and on the
   banner's buttons the ring takes the text colour. */
.cookie-settings {
  appearance: none; border: 0; background: none; padding: 1px 0; min-height: 24px;
  font: inherit; color: inherit; text-align: left; cursor: pointer;
  text-decoration: underline; text-underline-offset: 3px;
}
.cookie-settings[hidden] { display: none; }
.ftr .cookie-settings { text-decoration: none; }
.ftr .cookie-settings:hover { text-decoration: underline; }
.cookie-settings:focus-visible { outline: 3px solid currentColor; outline-offset: 3px; }
/* White rather than the text colour: hovering an answer turns its text the
   banner's own #1A1A1A, and a currentColor ring vanished with it. Astra. */
.cookies__btn:focus-visible { outline: 3px solid #fff; outline-offset: 3px; }
