/* Event page. Mobile-first, because the audience is overwhelmingly on a phone. */

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

body {
  margin: 0;
  background: var(--crs-bg);
  color: var(--crs-text);
  font-family: var(--crs-font);
  font-weight: var(--crs-weight-light);
  font-size: var(--crs-step-0);
  line-height: 1.5;   /* 24px on a 16px base, as the existing site does */
  /* NO -webkit-font-smoothing. It was `antialiased` here, and on a dark
     background that drops subpixel rendering -- at weight 300 it takes real
     substance out of the letterforms. Measured on the rendered pages: body copy
     computed `antialiased` on ours and `auto` on theirs, and side by side at the
     same family, size, weight and background the `auto` column is visibly
     heavier and brighter.

     CORRECTION to what I first wrote here: the existing site's stylesheet DOES
     contain `-webkit-font-smoothing: antialiased`. It just never reaches body
     copy -- their paragraphs compute `auto`. I had it on `body`, so it
     inherited into everything. "They do not use it" was wrong; "it does not
     apply to their text" is right, and the difference matters because the first
     version makes their stylesheet look like it agrees with us.

     The kerning, ligatures and oldstyle figures below come from the same
     comparison. */
  font-feature-settings: "kern" 1, "liga" 1, "pnum" 1, "tnum" 0, "onum" 1, "lnum" 0, "dlig" 0;
}

a { color: inherit; }
a:focus-visible, button:focus-visible {
  outline: 2px solid var(--crs-red-bright);
  outline-offset: 3px;
}

.wrap {
  width: 100%;
  max-width: var(--crs-container);
  margin-inline: auto;
  padding-inline: var(--crs-gutter-mobile);
}
@media (min-width: 60rem) { .wrap { padding-inline: var(--crs-gutter); } }

/* Headings are RED and UPPERCASE on this site, at weight 500. Ours were white,
   sentence case and 600 -- three differences at once, in the element a reader
   looks at first, which is most of why the two halves read as two sites. */
h1, h2, h3 {
  color: var(--crs-red);
  font-weight: var(--crs-weight-medium);
  text-transform: uppercase;
  letter-spacing: normal;
}
/* A heading that is also a link keeps the heading's own colour and drops the
   underline: the existing site never underlines a heading, and an underlined
   one reads as body copy that happens to be big. */
h1 a, h2 a, h3 a { color: inherit; text-decoration: none; }
/* Balanced lines (2026-10-24, #393): the renamed headings are longer, and an
   uppercase heading that breaks with one word alone on its last line
   ("CHOOSE YOUR CRAZY RACING / SENSATION") reads as a mistake. Balance never
   adds a line; it only evens out the ones there are. */
h1, h2, h3 { text-wrap: balance; }
h1 a:hover, h2 a:hover, h3 a:hover { color: var(--crs-red-bright); }

h1 { font-size: var(--crs-step-3); line-height: 1.2; }
h2 { font-size: var(--crs-step-2); line-height: 1.2; }
/* h3 lands at 18-22px, below the 24px where 3:1 is allowed, so it takes the
   legible red rather than the fill red. */
h3 { font-size: var(--crs-step-1); line-height: 1.3; color: var(--crs-red-text); }

/* An in-page link lands its target below the sticky header, not under it.
   The Pro Rider hero CTA jumps to #what-is-it (2026-10-14), the Sleepliner one
   to #enquiry-heading, the FAQ to its item ids; without this the bar covers
   the heading the reader was sent to. Measured on the built pages, all four
   locales, 2026-10-14: the bar is 134.6px while the menu wraps and 53.2px
   once it is one row, so each value is that height plus a little air. Since
   2026-10-24 (#393) the menu is one row only from 56rem, so the short offset
   starts there too; 135px still holds for the two-line bar measured at
   360-430px that day.
   Above the header section on purpose: header-css.js copies only what lies
   between the Header and Hero markers, so the mirrored pages, whose layout
   is the vendor's, do not get this. */
html { scroll-padding-top: 9rem; }
@media (min-width: 56rem) {
  html { scroll-padding-top: 4.5rem; }
}

/* ---------- Header ---------- */

.site-header {
  /* Sticky, not fixed. The vendor uses fixed with a hardcoded body offset, which
     only works because its bar is a constant 43px. Sticky behaves the same on
     scroll and reserves its own space at whatever height it ends up, so no
     offset can drift out of step with the bar.

     The figure that used to be in this comment was wrong twice over. It said a
     fixed bar "hid 77px of every page"; the wrapped header actually measured
     135px. Both numbers are history now that the menu is a single scrolling row
     rather than a wrapping one. Sticky stays anyway: it is still the version
     that cannot go out of step with its own height. */
  position: sticky;
  top: 0;
  z-index: 99;
  background: var(--crs-surface-raised);   /* #2d2d2d, as the existing header is */
  padding-block: var(--crs-space-xs);
  /* Restated here rather than inherited from body, because on the mirrored
     pages this header sits inside the vendor's body, not ours. There it is
     `line-height: 1.5em` -- an em value, so it inherits as a fixed pixel figure
     instead of scaling with each element's own size -- and a shorter font stack.
     Same values our body has; on a generated page these change nothing. */
  font-family: var(--crs-font);
  font-weight: var(--crs-weight-light);
  line-height: 1.5;
}

/* THE VENDOR'S RESET, ANSWERED ONCE FOR THE WHOLE BAR.

   On the mirrored pages (the home pages above all) site/dist/style.css loads
   before site-header.css, and three of its element rules land in this header:

     a, img, li, ul, ... { line-height: 1; font-weight: inherit; ... }
     a { font-weight: 600 }
     nav ul:before, nav ul:after { display: table; content: " " }

   The first two shrank every menu line from 24px to 16px and made the logo and
   the language links bold. The third is the one a tester saw. It is a float
   clearfix, and our list is a flex container, so the two pseudo-elements become
   two empty flex items -- each still owed the 20px column gap. At 390px the
   first row therefore started 20px in, "Events" no longer fitted on it, and the
   menu wrapped as "Pro Rider Experience / Track Events" over "Events / Circuits
   / FAQ" on the home page against three-over-two everywhere else. Measured, not
   read: x=40 for the first item on /en/, x=20 on /en/experience-track-riding/.

   Every selector here outranks the vendor's (one class beats bare elements), and
   these come before the header's own rules, so `.site-nav a` and the language
   menu's bold current item still win where they mean to. On a generated page
   they restate what inheritance already gives. */
.site-header :is(a, ul, li, img) { line-height: inherit; font-weight: inherit; }
.site-header ul::before,
.site-header ul::after { content: none; }

.site-header .wrap { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--crs-space-s) var(--crs-space-m); }

/* The logo shrinks, and it has to.
   It used to be a flat `height: 2.25rem` with no reason written down, which
   rendered 123x36 at EVERY width -- 31.5% of a 390px viewport, against the
   existing site's 50x15 on the same phone. That single line was the largest
   part of why our header stood 138px tall there where theirs stands 43.
   1.5rem on a phone is 82x24: still comfortably legible, and it lets the bar
   come down to something close to the one visitors meet on the home page. */
.site-header img { display: block; height: 1.5rem; width: auto; }

@media (min-width: 46rem) {
  .site-header img { height: 2.25rem; }
}

/* The menu that makes these pages reachable from each other.

   TWO LINES ON A PHONE, one row from 56rem up. (46rem until 2026-10-24: the
   renamed products, #393, no longer fit one row below 56rem. Between 46 and
   56rem the logo already grows to 2.25rem while the menu still wraps.)

   The history is worth keeping because both extremes were tried this week and
   both were wrong for a different reason.

   It wrapped originally, and the bar stood 135px tall at 390px -- 16% of the
   screen against 43px for the header on the home page. So it was made a single
   row that scrolled horizontally: 51px, everything on one line.

   Jimmy, 2026-09-30, on that version: "On mobile the menu should be back 2
   lines. One line is not usable." He is right, and it is not a matter of taste.
   In a 390px viewport the row shows "Pro Rider Experience" and the first letter
   of "Track Events". The other four items exist behind a horizontal swipe most
   people will never make, on a menu whose whole job is to prove those pages are
   there. A menu you have to discover is not a menu.

   So it wraps again, deliberately, and the height is the price. It is a much
   smaller price than it was: the logo now shrinks to 24px, so the bar is far
   below the 135px of the first version. Measured numbers are on the media query
   that compensates for it in header-css.js -- the mirrored pages reserve room
   for this bar, and that reservation has to track its height or the first line
   of the privacy page hides underneath.

   A collapsing <details> menu was also tried and abandoned: a closed <details>
   gives its content no usable box, so the desktop row measured zero wide. The
   reasoning and the numbers are in site-nav.js, above the nav markup. */
.site-nav {
  flex: 1 1 100%;
  order: 3;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--crs-space-s);
}
.site-nav ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--crs-space-m);
  margin: 0;
  padding: 0;
  list-style: none;
}
.site-nav li { flex: none; }

/* 16px at weight 500 in white, not 14px at 300 in grey: the existing menu is
   the same size as its body copy and fully opaque. */
.site-nav a {
  display: inline-block;
  padding-block: 0.35rem;
  text-decoration: none;
  font-size: var(--crs-step-0);
  font-weight: var(--crs-weight-medium);
  color: var(--crs-text);
  border-bottom: 2px solid transparent;
}
/* Hover had no effect at all: this rule set the colour the link already had,
   white on white, so a pointer over the menu changed nothing. The existing site
   fades both hover and the active item to 50% white; fading is wrong here
   because our current item is marked with a red underline and a fade would read
   as "disabled", so hover borrows the same underline at half strength instead.
   Keyboard focus gets it too -- a menu that only answers to a mouse is a menu
   half the people cannot see themselves moving through. */
.site-nav a:hover,
.site-nav a:focus-visible { border-bottom-color: var(--crs-red-text); }

/* The account item: an icon among words, so it needs its own sizing.
   Inheriting the nav's font-size would scale the SVG to the line box and give a
   16px glyph beside 16px text, which reads as a mistake rather than a control.
   1.25rem sits on roughly the same optical line as the cap height next to it.
   It is no longer inside the <nav> (2026-10-24, #393): see the next rule. */
.site-nav__account { display: flex; align-items: center; color: var(--crs-text); padding: 0.4rem 0.2rem; }
/* On a phone the account icon rides on the logo row, beside the language
   control, so the menu gets the full width: with it in the menu's row the two
   product names ("Experience Track Riding", "Bring Your Own Bike") no longer
   fit one line below 385px and the menu went to three. Measured 2026-10-24:
   two lines at 360px and up in all four locales; three only at 320. */
.site-header .site-nav__account { margin-inline-start: auto; }
/* Out of the <nav>, the icon lost `.site-nav a:hover/:focus-visible`, and on
   the mirrored pages the vendor's `a:focus { outline: 0 }` wins over our global
   focus ring, which header-css.js does not copy. So the icon carries the nav
   links' underline (as an inset shadow: a 2px border made the logo row, and so
   the bar, 2px taller) and the site's focus ring itself, specific enough (0,3,0) to
   beat the vendor's (0,1,1). A control nobody can see the keyboard reach is a
   control half the people cannot use. */
.site-header .site-nav__account:hover,
.site-header .site-nav__account:focus-visible { color: var(--crs-text); box-shadow: inset 0 -2px 0 var(--crs-red-text); }
.site-header .site-nav__account:focus-visible { outline: 2px solid var(--crs-red-bright); outline-offset: 3px; }
.site-nav__icon { display: block; width: 1.25rem; height: 1.25rem; }

/* The current section hangs off aria-current, the same attribute a screen
   reader announces, so the highlight cannot drift from a class someone forgot. */
.site-nav [aria-current="page"] {
  color: var(--crs-text);
  border-bottom-color: var(--crs-red);
}

@media (min-width: 56rem) {
  /* Everything fits beside the logo here, so the nav comes off its own line and
     sits where it did before. `margin-inline-start: auto` is load-bearing and
     was dropped by accident once already: the bar is `justify-content:
     space-between` with three children, so without it the spare width splits
     into two equal gaps and the menu lands in the MIDDLE of the bar instead of
     beside the language control. Measured at 1440px: 360px without it, 20 with. */
  .site-nav { flex: 0 1 auto; order: initial; margin-inline-start: auto; }
  .site-header .site-nav__account { margin-inline-start: 0; }
}

/* The language switcher, shaped like the mirror's dropdown but built from
   <details>/<summary>. No JavaScript and no CDN icon: generated pages load
   nothing from another origin (a test asserts it) and ship no script at all,
   which is also what keeps them working on the preview build where the
   mirror's own scripts are not loaded. The chevron is drawn here. */
.langs { position: relative; flex: none; }
.langs > summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 0.35em;
  padding: 0.35rem 0.1rem;
  font-size: var(--crs-step--1);
  color: var(--crs-text-muted);
}
.langs > summary::-webkit-details-marker { display: none; }
.langs > summary::after {
  content: "";
  width: 0.45em;
  height: 0.45em;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg) translate(-0.1em, -0.1em);
  transition: transform 120ms ease;
}
.langs[open] > summary::after { transform: rotate(-135deg) translate(-0.15em, -0.15em); }
/* Our header's only motion. Inside the header block, so the mirrored pages' copy of
   the header (site-header.css) honours it too; the vendor's own animations there are
   not ours to change. */
@media (prefers-reduced-motion: reduce) {
  .langs > summary::after { transition: none; }
}
.langs > summary:hover { color: var(--crs-text); }

.langs__menu {
  position: absolute;
  right: 0;
  z-index: 10;
  min-width: 9rem;
  display: flex;
  flex-direction: column;
  padding: var(--crs-space-xs) 0;
  background: var(--crs-surface-raised);
  border: 1px solid var(--crs-border);
  border-radius: var(--crs-radius);
}
.langs__menu a, .langs__menu span {
  padding: 0.45rem var(--crs-space-s);
  text-decoration: none;
  font-size: var(--crs-step--1);
  color: var(--crs-text-muted);
  white-space: nowrap;
}
.langs__menu a:hover { color: var(--crs-text); background: var(--crs-surface); }
.langs__menu [aria-current="true"] { color: var(--crs-text); font-weight: var(--crs-weight-bold); }

/* ---------- Hero ---------- */

/* A hero that carries a photograph is a full screen, like #header on the
   existing home page (min-height: 100svh). Ours was 0.46 of a screen: half a
   photograph with text under it, which is the FIRST thing a visitor sees and
   so the first thing that said "different website". A hero with no photograph
   stays a band -- there is nothing to fill a screen with. */
.hero { padding-block: var(--crs-section-pad); }
.hero.has-photo {
  min-height: 100svh;
  max-height: 75vw;
  display: flex;
  align-items: center;
}
/* Shorter on a phone, because the photograph has to survive the crop. At 70svh
   the box was 390x591 -- taller than wide -- and `cover` on a 3:2 photograph
   then showed 44% of its width, measured on the live page. At 56svh it is
   390x473 and shows 55%.

   Still a crop, and it stays one. The heading and lede sit OVER the photograph
   and need about 330px, so on a 390px screen this box cannot be landscape --
   there is no CSS that shows the whole photograph here. The alternative was to
   stack it above the text on phones, which shows all of it and changes how the
   page opens. Put to Jimmy with both measurements on 2026-09-28; he chose to
   keep the hero as it is. Recorded so the next person reads a decision rather
   than an open question. */
@media (max-width: 48rem) { .hero.has-photo { min-height: 56svh; max-height: none; } }

/* Biased upward rather than centred. These are panning shots: the rider sits on
   or above the middle line and the bottom of the frame is tarmac. Centring the
   crop spends the visible band on empty track. 40% is a default for the KIND of
   photograph CRS publishes, not for any one of them -- the archive cannot tell
   us where the subject is, and guessing per photo is not something CSS can do.
   If a particular photograph crops badly, the answer is to tag a better-framed
   one, which is now a thing CRS can do without us -- and which is why keeping
   the hero as it is costs less than it would have before the slots existed. */
@media (max-width: 48rem) { .hero .hero-photo { object-position: 50% 40%; } }

/* The photo is a background that is still a real <img>, so it can be sized,
   lazily decoded and given an alt -- and so the browser reserves its space. The
   gradient is what keeps white text legible over a photo whose brightness we do
   not control; without it the eyebrow and dates fail contrast on the light half
   of the image. */
.hero.has-photo { position: relative; isolation: isolate; overflow: hidden; }
.hero .hero-photo {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.hero.has-photo::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(to right, rgb(10 10 10 / 0.92) 0%, rgb(10 10 10 / 0.75) 55%, rgb(10 10 10 / 0.45) 100%);
}
/* On a narrow screen the text runs the full width, so a left-to-right gradient
   leaves the last lines sitting on the bright part of the photo -- the dates
   were unreadable on a phone. Below the breakpoint the wash goes bottom-up
   instead, which is where the text is. */
@media (max-width: 52rem) {
  .hero.has-photo::after {
    background: linear-gradient(to bottom, rgb(10 10 10 / 0.55) 0%, rgb(10 10 10 / 0.82) 45%, rgb(10 10 10 / 0.94) 100%);
  }
}
.hero .eyebrow {
  margin: 0 0 var(--crs-space-xs);
  font-size: var(--crs-step--1);
  font-weight: var(--crs-weight-bold);
  text-transform: uppercase;
  color: var(--crs-red-text);
}
.hero h1 {
  margin: 0;
  /* Inherits size, weight, colour and case from the base rule above, which is
     the point: three places setting an h1 is how they drift apart. */
  line-height: 1.2;
}
.hero .dates {
  margin: var(--crs-space-s) 0 0;
  font-size: var(--crs-step-1);
  color: var(--crs-text-muted);
}
.hero .lede { margin: var(--crs-space-m) 0 0; }

.badge {
  display: inline-block;
  margin-top: var(--crs-space-m);
  padding: 0.35em 0.8em;
  border: 1px solid currentColor;
  border-radius: var(--crs-radius);
  font-size: var(--crs-step--1);
  font-weight: var(--crs-weight-bold);
  text-transform: uppercase;
}
.badge--soldout { color: var(--crs-red-text); }
.badge--past { color: var(--crs-text-muted); }

/* ---------- Participation options ---------- */

.options { padding-block: var(--crs-section-pad); }
.options h2 {
  margin: 0 0 var(--crs-space-m);
}
/* Cards, never a table: the build specification forbids presenting the options
   as a price comparison, and a grid of equals is also simply the honest shape. */
.options ul { display: grid; gap: var(--crs-space-m); list-style: none; margin: 0; padding: 0; }
@media (min-width: 46rem) { .options ul { grid-template-columns: 1fr 1fr; } }

.option {
  background: var(--crs-bg);
  border: 0;
  border-radius: var(--crs-radius);
  padding: 25px;
  box-shadow: 10px 10px 23px 0 rgb(0 0 0 / 0.2);
  display: flex;
  flex-direction: column;
  gap: var(--crs-space-s);
}
.option h3 { margin: 0; }
.option p { margin: 0; color: var(--crs-text-muted); }
.option .cta { margin-top: auto; }

/* Measured off the existing site's .button: 12px 20px, radius 5px, 16px at
   weight 300, uppercase, white on red. Ours was smaller, bolder and tracked
   out, which made every call to action look like a different component. */
.cta {
  display: inline-block;
  padding: 12px 20px;
  background: var(--crs-red);
  color: var(--crs-text);
  font-weight: var(--crs-weight-light);
  font-size: var(--crs-step-0);
  letter-spacing: normal;
  text-transform: uppercase;
  text-decoration: none;
  border-radius: var(--crs-radius);
}
.cta:hover { background: #ad0910; }   /* darker, as the existing site does */
.cta[aria-disabled="true"] {
  background: transparent;
  border: 1px solid var(--crs-border);
  color: var(--crs-text-muted);
  pointer-events: none;
}

/* An ENQUIRY, for a product CRS sells but we cannot sell online yet (no capacity
   from CRS). Outlined rather than solid red: it is a real action and must be
   pressable, but it is not the same promise as "book your place" -- somebody will
   answer by hand. The note below it says so in words, because a differently styled
   button is not an explanation. */
.cta--enquire {
  background: transparent;
  border: 1px solid var(--crs-red);
  color: var(--crs-text);
}
.cta--enquire:hover { background: rgba(222, 11, 20, 0.15); }
/* The spec's informing CTA, demoted to a quiet link beside the booking button: a
   visitor who is not ready to buy should not have to go back to a search engine. */
.cta-secondary {
  display: inline-block;
  margin-top: 8px;
  color: var(--crs-text-muted);
  font-size: var(--crs-step--1);
  text-decoration: underline;
}
.cta-secondary:hover { color: var(--crs-text); }

.cta-note {
  display: block;
  margin-top: 8px;
  max-width: 34ch;
  color: var(--crs-text-muted);
  font-size: var(--crs-step--1);
}
/* The line the live state writes into ("3 places left", "not yet open for online
   booking"), reserved by the build so the answer arriving from the API moves nothing
   (#180). One line high when empty; a longer note simply uses the lines it needs. */
.cta-note[data-live-note] { min-height: 1.5em; line-height: 1.5; }

/* ---------- A combi's programme (#324) ---------- */

/* One row per leg: which days, which circuit, how many days. A list rather than a
   table, so a row wraps on a phone instead of scrolling sideways. */
.programme { padding-block: var(--crs-section-pad); border-top: 1px solid var(--crs-border); }
.programme h2 { margin: 0 0 var(--crs-space-m); }
.programme-lead { margin: 0 0 var(--crs-space-m); color: var(--crs-text-muted); }
.programme ol { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--crs-space-s); }
.programme-leg { padding-left: var(--crs-space-s); border-left: 3px solid var(--crs-red); }
.programme-leg a { color: var(--crs-text); }
.programme-days { color: var(--crs-text-muted); white-space: nowrap; }

/* ---------- The circuit ---------- */

.circuit { padding-block: var(--crs-section-pad); border-top: 1px solid var(--crs-border); }
.circuit h2 { margin: 0 0 var(--crs-space-m); }
.circuit h3 {
  margin: var(--crs-space-l) 0 var(--crs-space-xs);
}
.circuit .prose > h3:first-child { margin-top: 0; }
.circuit p { margin: 0 0 var(--crs-space-s); }

/* The prose and the layout sit side by side on a wide screen and stack on a
   narrow one. The layout keeps its own column rather than floating, so a long
   paragraph cannot wrap around it into an unreadable 12ch gutter. */
.circuit-body {
  display: grid;
  gap: var(--crs-space-l);
  margin-top: var(--crs-space-l);
}
@media (min-width: 52rem) {
  .circuit-body { grid-template-columns: 1fr auto; align-items: start; }
}

.highlights { margin: 0; padding: 0; list-style: none; }
.highlights li {
  position: relative;
  padding-left: var(--crs-space-m);
  margin-bottom: var(--crs-space-xs);
  color: var(--crs-text-muted);
}
.highlights li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 0.45rem;
  height: 0.45rem;
  background: var(--crs-red);
}

/* The grid lays out PAIRS, not terms and values independently: each dt/dd is
   wrapped in a div, or the columns interleave and every value lands next to the
   wrong label. */
.facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--crs-space-m);
  margin: 0;
}
.facts > div { display: flex; flex-direction: column; gap: var(--crs-space-2xs); }
.facts dt {
  font-size: var(--crs-step--1);
  text-transform: uppercase;
  color: var(--crs-text-muted);
}
.facts dd { margin: var(--crs-space-2xs) 0 0; font-size: var(--crs-step-1); font-weight: var(--crs-weight-medium); }

/* ---------- Gallery ---------- */

.gallery { padding-block: var(--crs-section-pad); border-top: 1px solid var(--crs-border); }
.gallery h2 { margin: 0 0 var(--crs-space-m); }
.gallery ul {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: var(--crs-space-m);
  margin: 0;
  padding: 0;
  list-style: none;
}
.gallery img {
  width: 100%;
  height: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: var(--crs-radius);
}

/* ---------- Events overview ---------- */

.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.listing { padding-block: var(--crs-section-pad); }
.event-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--crs-space-s); }

/* A row, not a card grid: the list is read down the left edge, and a date that
   lines up is what makes a season scannable. */
/* A row spans the list. `main li` is capped at the reading measure further down,
   which left every row 432px wide on a 1280px screen with 800px of nothing beside
   it; the row lays out its own columns, so the measure does not apply to it. */
.event-row { max-width: none; }
.event-row a {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--crs-space-2xs);
  padding: var(--crs-space-m);
  background: var(--crs-surface);
  border: 1px solid var(--crs-border);
  border-radius: var(--crs-radius);
  text-decoration: none;
}
.event-row a:hover { border-color: var(--crs-red); }
@media (min-width: 46rem) {
  .event-row a { grid-template-columns: 14rem 1fr auto; align-items: baseline; gap: var(--crs-space-m); }
}
.event-row .when { color: var(--crs-text-muted); font-variant-numeric: tabular-nums; }
.event-row .what { font-size: var(--crs-step-1); font-weight: var(--crs-weight-bold); }
/* The products and the place are what a reader scans the list for, so they are
   body size, not small print, and a step heavier than the light body: muted grey
   at 14px and weight 300 was the hardest text on the page to read (external
   review, 2026-10). Asked for as "regular, 400" -- but no 400 face is loaded
   (fonts.css ships 300, 500 and 600), and the font-matching rules resolve 400 to
   500. Writing 400 would name a weight the browser never draws, so this names
   the one it does. */
.event-row .how,
.circuit-card .how { color: var(--crs-text-muted); font-size: var(--crs-step-0); font-weight: var(--crs-weight-medium); }
.event-row .flag { color: var(--crs-red-text); font-weight: var(--crs-weight-bold); font-size: var(--crs-step--1); text-transform: uppercase; }
/* Places left, under the products it counts. Reserved by the build wherever a
   capacity exists, so the count arriving from the API does not move the row (#180). */
.event-row .live-count { display: block; min-height: 1.5em; line-height: 1.5; color: var(--crs-red-text); }

/* ---------- The events overview's cards (#339) ----------

   Laid out as the live events page lays out its own, MEASURED there with
   getComputedStyle at 390 and 1280px on 2026-10-15 (www.crs-racing.com/en/events):
   a month heading, then per date a bordered card with the circuit panel on the left
   (country flag in its corner) and on the right the date line, the name and one line
   per product with its button. The circuit pages keep their rows; only the overview
   is a card list. */

/* The live page opens on a 400px photograph band, not a full screen. On a phone the
   heading and lede still need their room, so the general hero rule stands there. */
@media (min-width: 48rem) { .hero--band.has-photo { min-height: 400px; max-height: none; } }

.listing--events { padding-top: var(--crs-space-l); }
.month + .month { margin-top: var(--crs-space-l); }
.month > h2 { margin: 0 0 25.6px; }   /* theirs: 32px/38.4px, then 0.8em */
.event-list--cards { gap: var(--crs-space-l); }   /* theirs: 30px between cards */

/* An <li>, so `main li` would cap it at the reading measure; it lays out its own
   columns.

   The whole card is the button (Jimmy, 2026-10-15: "Gans de event banner zou een
   knop moeten zijn"), by the stretched-link pattern: the title's one <a> draws an
   empty ::after over the card, so a click anywhere on it -- the circuit panel, the
   date line, a product line, the combi band -- follows that link, while the tab
   order and a screen reader still meet one link with the event's name. The card is
   the positioned ancestor the ::after fills; nothing inside it may be interactive,
   or the overlay would swallow its clicks. */
.event-card {
  position: relative;
  cursor: pointer;
  max-width: none;
  display: flex;
  flex-direction: column;
  background: var(--crs-bg);
  border: 1px solid var(--crs-card-border);
  border-radius: var(--crs-radius-card);
  overflow: hidden;
  transition: transform 0.6s, box-shadow 0.6s;
}
/* Theirs lifts 5px and casts a red glow; a keyboard gets the same card and a white
   ring round all of it (as the booking page's focus), not a ring round the title
   alone, which would claim only the title is the target. */
.event-card:hover,
.event-card:focus-within { transform: translateY(-5px); box-shadow: 0 10px 30px rgb(221 11 20 / 0.3); }
.event-card:focus-within { outline: 2px solid var(--crs-text); outline-offset: 2px; }
/* A mouse click focuses the link too; the ring is for the keyboard, so it goes
   when the focus is not :focus-visible (a browser without :has() keeps it). */
.event-card:focus-within:not(:has(:focus-visible)) { outline: none; }
.event-card .what a::after { content: ''; position: absolute; inset: 0; }
.event-card .what a:focus-visible { outline: none; }
@media (prefers-reduced-motion: reduce) {
  .event-card { transition: none; }
  .event-card:hover,
  .event-card:focus-within { transform: none; }
}

/* The circuit panel: 5:4 on a phone, 16:9 from 480px, a square 30% column from
   768px and a 16:9 40% column from 1000px -- their four steps, unchanged. */
.event-visual {
  position: relative;
  display: block;
  aspect-ratio: 5 / 4;
  background: var(--crs-surface-raised);
}
@media (min-width: 30rem) { .event-visual { aspect-ratio: 16 / 9; } }
/* Width and min-width, as theirs: a flex-basis lets the ratio feed back into the
   width once the text column makes the card taller than the panel. */
@media (min-width: 48rem) {
  .event-card { flex-direction: row; }
  .event-visual { flex: none; width: 30%; min-width: 30%; aspect-ratio: 1 / 1; }
}
@media (min-width: 62.5rem) { .event-visual { width: 40%; min-width: 40%; aspect-ratio: 16 / 9; } }
.event-visual .track {
  position: absolute;
  top: 50%;
  left: 50%;
  width: auto;
  height: 70%;
  max-width: 80%;
  transform: translate(-50%, -50%);
  filter: drop-shadow(0 3px 4px rgb(107 0 0 / 0.75));
}
/* Their flag: a 20px circle, 10px in from the panel's corner. */
.event-card .country { display: block; width: 20px; height: 20px; border-radius: 50%; }
.event-visual > .country { position: absolute; top: 10px; left: 10px; }

/* A combi has no single layout to show, so it gets their red COMBI band: across the
   top of the card on a phone, a 52px column reading bottom-up beside the text on a
   wider screen. Its flags sit in the band. */
.event-visual--combi {
  aspect-ratio: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 40px;
  background: var(--crs-red);
  color: var(--crs-text);
  text-decoration: none;
}
.event-card .event-visual--combi .flag {
  margin: 0;
  padding: 0;
  background: none;
  color: var(--crs-text);
  font-size: var(--crs-step-0);
  font-weight: var(--crs-weight-bold);
  text-transform: uppercase;
}
.event-visual--combi .countries { position: absolute; top: 10px; left: 10px; display: flex; gap: 4px; }
@media (min-width: 48rem) {
  .event-visual--combi { width: 52px; min-width: 52px; aspect-ratio: auto; }
  .event-visual--combi .combi-band { writing-mode: vertical-rl; transform: rotate(180deg); }
  .event-visual--combi .countries { flex-direction: column; left: 16px; }
}
@media (min-width: 62.5rem) { .event-visual--combi { width: 52px; min-width: 52px; aspect-ratio: auto; } }

.event-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: var(--crs-space-m) 15px;
}
@media (min-width: 48rem) { .event-body { padding: 30px; } }

/* Date, days, place: white, uppercase and the heaviest face we load (theirs asks for
   700, which Rajdhani here does not ship; 600 is what a browser would draw). */
.facts-line {
  display: flex;
  flex-wrap: wrap;
  gap: 4px var(--crs-space-s);
  font-weight: var(--crs-weight-bold);
  text-transform: uppercase;
  line-height: 1.25;
}
.facts-line > span { display: inline-flex; align-items: center; gap: 4px; }
.facts-line .icon { flex: none; width: 16px; height: 16px; }

.event-card .what { margin: 5px 0 var(--crs-space-m); }

/* Our extras -- combi, fully booked -- as a small label in the card, the way theirs
   shows "4 spots left!". */
.event-card .flag {
  align-self: flex-start;
  margin: 0 0 10px;
  padding: 6px 20px;
  border-radius: 25px;
  background: rgb(222 11 20 / 0.12);
  color: var(--crs-red-text);
  font-size: var(--crs-step--1);
  font-weight: var(--crs-weight-bold);
  line-height: 1;
}

/* One line per product: their tinted strip, the name and its places label beside
   it, wrapping under the name when the line is too short. No button: the title is
   the way in (Jimmy, 2026-10-15), so the strip no longer reserves a 43px button
   height -- it is as tall as the label it carries. */
.experience {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  margin-bottom: 10px;
  padding: 10px 15px;
  background: rgb(255 255 255 / 0.05);
  border-radius: var(--crs-radius);
}
.experience:last-child { margin-bottom: 0; }
@media (min-width: 48rem) { .experience { padding-inline: 20px; } }
.experience .places {
  padding: 6px 20px;
  border-radius: 25px;
  background: rgb(255 165 0 / 0.1);
  color: var(--crs-amber);
  font-size: var(--crs-step--1);
  font-weight: var(--crs-weight-bold);
  line-height: 1;
  white-space: nowrap;
}
/* Reserved until the API answers (#180): the space is there, the empty pill is not. */
.experience .places:empty { visibility: hidden; }
.experience .places[data-state="full"] { background: rgb(222 11 20 / 0.12); color: var(--crs-red-text); }

.breadcrumb { padding-top: var(--crs-space-m); font-size: var(--crs-step--1); }
.breadcrumb a { color: var(--crs-text-muted); text-decoration: none; }
.breadcrumb a:hover { color: var(--crs-text); }

/* Location, and the track layout */

.event-row .where,
.circuit-card .where {
  display: block;
  font-size: var(--crs-step-0);
  font-weight: var(--crs-weight-medium); /* see .event-row .how */
  color: var(--crs-text-muted);
}
.hero .location {
  margin: var(--crs-space-2xs) 0 0;
  font-size: var(--crs-step-1);
  color: var(--crs-text-muted);
}
.tracks {
  display: flex;
  flex-wrap: wrap;
  gap: var(--crs-space-l);
  margin-top: var(--crs-space-l);
}
/* The artwork is dark-on-transparent and sits on a dark page, so it gets a
   surface of its own rather than disappearing into the background. */
.tracks .track {
  width: 100%;
  max-width: 18rem;
  height: auto;
  padding: var(--crs-space-m);
  background: var(--crs-surface-raised);
  border-radius: var(--crs-radius);
}

/* ---------- Circuit pages ---------- */

/* The circuit name in an event's section heading is a link; it should read as
   the heading it is, not as body-copy link styling. */
.circuit h2 a { color: inherit; text-decoration-color: var(--crs-red); text-underline-offset: 4px; }
.circuit h2 a:hover { color: var(--crs-red); }

/* A past date is deliberately NOT a link -- it would 301 away from the very
   circuit the reader is on. It still has to look like a row, so it takes the
   event-row layout with the affordances removed. */
.event-row.is-past > div {
  display: grid;
  gap: var(--crs-space-2xs) var(--crs-space-m);
  padding: var(--crs-space-m);
  color: var(--crs-text-muted);
}
@media (min-width: 46rem) {
  .event-row.is-past > div { grid-template-columns: 14rem 1fr auto; align-items: baseline; }
}
.event-row.is-past { cursor: default; }
.event-row.is-past .what { font-weight: var(--crs-weight-medium); }

.listing.past { border-top: 1px solid var(--crs-border); padding-top: var(--crs-space-l); }
.listing .note {
  margin: 0 0 var(--crs-space-m);
  color: var(--crs-text-muted);
  font-size: var(--crs-step--1);
}

/* The circuit index is a list of cards with the events overview's dynamic
   (Jimmy, 2026-10-15): each <li> is also an .event-card, so the stretched link,
   the hover lift and glow, the keyboard ring and the reduced-motion rule are the
   overview's own rules, not a copy. Only what a circuit card has and an event card
   does not is set here: the flag beside the name instead of in a panel's corner,
   and the place and count lines under it. */
/* Matches .event-list, including the gap it was missing -- without it the index
   rows butt together. */
.circuit-list { display: grid; gap: var(--crs-space-s); list-style: none; margin: 0; padding: 0; }
.circuit-card .event-body { padding: var(--crs-space-m) 15px; }
@media (min-width: 48rem) { .circuit-card .event-body { padding: var(--crs-space-m) 30px; } }
/* The flag sits on the name's first line when a long name wraps, centred on that
   line (1lh; a browser without it puts the flag at the line's top). */
.circuit-card .what { display: flex; align-items: flex-start; gap: 10px; margin: 0 0 var(--crs-space-2xs); }
.circuit-card .country { flex: none; margin-top: calc((1lh - 20px) / 2); }
.circuit-card .where,
.circuit-card .how { margin: 0; }
.circuit-card .how { margin-top: var(--crs-space-2xs); }
.crosslink { margin: var(--crs-space-s) 0 0; font-size: var(--crs-step--1); }

/* ---------- Product pages ---------- */

.product-hero .mandatory { margin: var(--crs-space-s) 0 0; }
/* Spec section 4: the second line is the one that answers the objection most
   visitors actually have, so it is not allowed to read as small print. */
.product-hero .mandatory .claim { display: block; font-size: var(--crs-step-1); font-weight: var(--crs-weight-medium); }
.product-hero .mandatory .required { display: block; margin-top: var(--crs-space-2xs); color: var(--crs-red-text); font-weight: var(--crs-weight-bold); }

/* 60px of vertical air, and every other section banded a shade darker. Both
   copied from the existing site: it alternates #242424 and #1d1d1d down the
   page, which is what gives it rhythm. Ours ran every section on one flat
   background with a hairline between, and read as a document rather than a
   site. */
.prod { padding-block: var(--crs-section-pad); }
.prod:nth-of-type(even) { background: var(--crs-band); }
.prod h2 { margin: 0 0 var(--crs-space-m); }
.prod h3 { margin: var(--crs-space-l) 0 var(--crs-space-2xs); color: var(--crs-text); }
/* Capped at the measure, not at 600px. 600 was lifted from the vendor's
   stylesheet without checking what it renders as: in this typeface it is 91
   characters, where the vendor's own paragraphs sit at 55-76. */
.prod p { max-width: var(--crs-measure); margin: 0 0 var(--crs-space-l); }
.prod .quote { font-size: var(--crs-step-1); font-weight: var(--crs-weight-medium); color: var(--crs-text); border-left: 3px solid var(--crs-red); padding-left: var(--crs-space-m); margin-bottom: var(--crs-space-m); }

/* The chronology is the argument: numbering it says "this is a sequence",
   which is true here and is why the markers are allowed. */
.phases { counter-reset: phase; list-style: none; margin: 0; padding: 0; }
.phases li { counter-increment: phase; position: relative; padding-left: 3.25rem; margin-bottom: var(--crs-space-l); }
.phases li::before {
  content: counter(phase, decimal-leading-zero);
  position: absolute; left: 0; top: 0.1em;
  font-family: var(--crs-font); font-size: var(--crs-step-1); font-weight: var(--crs-weight-bold);
  color: var(--crs-red-text);
}
.phases h3 { margin-top: 0; }

/* What is included on Pro Rider: a label and a sentence per item. One column
   on a phone; two from 46rem, each column one measure wide at most, so a
   sentence never runs past the cap that `main li` sets anyway. */
.includes { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--crs-space-l) var(--crs-space-m); list-style: none; margin: 0 0 var(--crs-space-l); padding: 0; }
@media (min-width: 46rem) { .includes { grid-template-columns: repeat(2, minmax(0, var(--crs-measure))); justify-content: start; } }
.includes li { position: relative; padding-left: var(--crs-space-m); }
.includes strong { display: block; margin-bottom: var(--crs-space-2xs); color: var(--crs-text); font-weight: var(--crs-weight-bold); }
.includes span { display: block; color: var(--crs-text-muted); }
.includes li::before { content: ""; position: absolute; left: 0; top: 0.62em; width: 0.45rem; height: 0.45rem; background: var(--crs-red); }

/* Both cards weigh the same on purpose: spec 5.2.1 forbids styling one as the
   cheap option and the other as the expensive one. */
.formats {
  display: grid;
  /* auto-fit, so the same rule holds the flagship's two formats and Track
     Events' three packages without either being left alone on a row. */
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: var(--crs-space-m);
  list-style: none; margin: 0; padding: 0;
}
/* Direct children only. Without the `>` the nested "what's included" list
   inherits the card's border and background, and every line item renders as
   its own little box. */
.formats > li {
  background: var(--crs-bg);
  border: 0;
  border-radius: var(--crs-radius);
  padding: 25px;
  box-shadow: 10px 10px 23px 0 rgb(0 0 0 / 0.2); display: flex; flex-direction: column; }
.formats .includes { display: block; margin-bottom: var(--crs-space-s); }
.formats .includes li { background: none; border: 0; padding: 0 0 0 var(--crs-space-m); display: list-item; }
.formats h3 { margin-top: 0; }
/* Red, not gold. Gold is absent from the existing site entirely -- measured
   across style.css, overrides.css and the rendered home page -- so a gold
   label is the one thing on the page that belongs to no palette here. The
   two PREVIEW markers below keep it on purpose: a colour the site never
   uses is what makes an unconfirmed fact read as unconfirmed. */
.formats .flag { align-self: flex-start; font-size: var(--crs-step--1); text-transform: uppercase; color: var(--crs-red-text); margin-bottom: var(--crs-space-xs); }
.formats .cta-wrap { margin-top: auto; }
.placeholder { color: var(--crs-gold); font-style: italic; }
/* Gold on red measures 2.21:1. Inside a panel the marker earns its
   "unconfirmed" reading from a dark inset rather than from a colour the
   background cancels. */
.stage__panel .placeholder {
  color: var(--crs-text);
  background: rgb(10 10 10 / 0.55);
  padding: 0.1em 0.4em;
  border-radius: 2px;
}

/* ---------- FAQ ---------- */

/* <details>/<summary> and no script: keyboard accessible for free, findable
   with the browser's own find-in-page, and it still works on the preview
   build where the mirror's scripts are not loaded. */
.faq-list { border-top: 1px solid var(--crs-border); }
.faq-item { border-bottom: 1px solid var(--crs-border); }
.faq-item summary {
  cursor: pointer;
  padding-block: var(--crs-space-s);
  font-size: var(--crs-step-0);
  font-weight: var(--crs-weight-medium);
  /* The default triangle is tiny and sits oddly against a 1.6 line-height, so
     it is replaced by a marker drawn at text size on the other side. */
  list-style: none;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--crs-space-m);
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after {
  content: "+";
  flex: none;
  color: var(--crs-red);
  font-size: var(--crs-step-1);
  line-height: 1;
}
.faq-item[open] summary::after { content: "\2013"; }
.faq-item summary:hover { color: var(--crs-text); }

.faq-answer { padding-bottom: var(--crs-space-s); color: var(--crs-text-muted); }
.faq-answer p { margin-block: 0 var(--crs-space-s); }
.faq-answer p:last-child { margin-bottom: 0; }
.faq-answer a { color: var(--crs-text); text-decoration: underline; }

/* Preview builds only. This is an instruction to CRS sitting inside a visitor
   answer, so it must never be mistaken for copy: gold, italic (from
   .placeholder) and boxed off. */
.faq-confirm {
  border-left: 2px solid var(--crs-gold);
  padding-left: var(--crs-space-s);
  font-size: var(--crs-step--1);
}

/* ---------- The coach ---------- */

/* Not the `.phases` list: those are steps in a sequence and are numbered. These
   are three facets of one person, so they are an unordered list, and each gets
   room to breathe because this section is the emotional argument of the page. */
.coach-points { list-style: none; margin: var(--crs-space-l) 0 0; padding: 0; display: grid; gap: var(--crs-space-l); }
@media (min-width: 60rem) { .coach-points { grid-template-columns: repeat(3, 1fr); gap: var(--crs-space-m); } }
.coach-points li { border-top: 2px solid var(--crs-red); padding-top: var(--crs-space-s); }
.coach-points h3 { margin: 0 0 var(--crs-space-xs); }
.coach-points p { margin: 0; color: var(--crs-text-muted); }

/* The FAQ hub: two routes, not two columns of answers. Equal weight on purpose
   -- neither product is the "default" question. */
.faq-hub { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--crs-space-m); }
@media (min-width: 46rem) { .faq-hub { grid-template-columns: 1fr 1fr; } }
.faq-hub li {
  background: var(--crs-bg);
  border: 0;
  border-radius: var(--crs-radius);
  padding: 25px;
  box-shadow: 10px 10px 23px 0 rgb(0 0 0 / 0.2);
  display: flex;
  flex-direction: column;
  gap: var(--crs-space-s);
}
.faq-hub h2 { margin: 0; }
.faq-hub p { margin: 0; color: var(--crs-text-muted); }
.faq-hub .cta-wrap { margin-top: auto; }

/* A row of photographs under a section.
   The column count is explicit per strip rather than `repeat(auto-fit,
   minmax(0, 1fr))`: with a zero minimum the browser cannot work out how many
   tracks to create, and the four-photo strip collapsed to a single narrow
   column while the three-photo one happened to look right -- the worst kind of
   bug, because it looks like it works.
   Portrait and landscape shots sit side by side, so the row is height-
   constrained and `object-fit: cover` keeps it level without cropping
   decisions leaking into the markup. */
.photo-strip {
  list-style: none;
  margin: var(--crs-space-l) 0 0;
  padding: 0;
  display: grid;
  gap: var(--crs-space-s);
  /* ONE column on a phone. Two columns of 169px turned every landscape
     photograph into a portrait crop showing 54% of its width -- measured on a
     390px screen -- which on an action shot is usually the half without the
     rider in it. One full-width photograph at its own shape beats two slices of
     one. */
  grid-template-columns: 1fr;
}
@media (min-width: 30rem) { .photo-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 60rem) {
  .photo-strip--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .photo-strip--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
/* `aspect-ratio`, not a fixed height. These photographs are all about 3:2, so a
   3:2 box crops nothing; a fixed 13rem height crops whatever the column width
   happens to leave over, which is a different crop at every breakpoint and a
   severe one on a phone. Explicit column counts rather than auto-fit: auto-fit
   with minmax(0, 1fr) silently collapsed the four-photo strip to one column
   once before. */
.photo-strip img {
  display: block;
  width: 100%;
  /* `height: auto` is not tidiness, it is what makes aspect-ratio work at all.
     Every one of these <img> tags carries width/height attributes (they have to,
     or the page jumps as each photograph loads), and those map to a presentation
     hint of `height: 933px`. aspect-ratio only decides the height when the
     height is auto -- so without this line the ratio computes, is ignored, and
     each photograph renders at its full intrinsic height: 933px tall on a phone.
     Measured in the browser; the stylesheet looked correct. */
  height: auto;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: var(--crs-radius);
}

/* Room above the footer on OUR pages. Deliberately outside the footer section:
   tools/lib/footer-css.js cuts that section into the mirrored pages' sheet, and
   the home page's last photograph meets its footer with no gap (2026-10-05: the
   home page stays exactly as it is). */
.site-footer { margin-top: var(--crs-section-pad); }

/* ---------- Footer ---------- */

/* ONE footer on every page, generated and mirrored alike (2026-10-14).

   The vendor's footer is the reference and these rules are its measured
   rendering, not its stylesheet: every value below was read with
   getComputedStyle off /nl/ at 390px and 1280px and checked again on ours.
   #242425, 30px top and bottom, 14px white text at weight 600, the logo 250 x 66
   in a 76px box, the 15 / 30 / 60px container gutters, and a bottom strip under
   a half-pixel hairline that holds the terms link and the credit at 40%.

   Below 768px everything stacks and centres; from 768px it is one row --
   logo left, contact in the middle, socials right -- with the bottom strip
   flush right.

   This section is ALSO the mirrored pages' footer stylesheet: footer-css.js cuts
   it out verbatim, between this marker and the next, into
   assets/site-footer.css. Two consequences:
   - only `.site-footer` selectors here, because the vendor's stylesheet is
     loaded next to it and none of its class names are ours;
   - every property the vendor's reset would otherwise supply is spelled out,
     because over there it does: its `a` is red, weight 600 and underlined, and
     its `p` and `ul` carry margins. */
.site-footer {
  position: relative;
  /* Above the home page's positioned photography, below the sticky header
     (99). The vendor had 999, which put the footer over the header in a short
     landscape viewport and over its own contact modal. */
  z-index: 1;
  overflow: hidden;
  padding: 30px 0;
  background: var(--crs-footer);
  color: var(--crs-text);
  font-family: var(--crs-font);
  font-size: var(--crs-step--1);
  font-weight: var(--crs-weight-light);
  line-height: 1.5;
  text-align: center;
}
.site-footer__inner {
  max-width: var(--crs-container);
  margin: 0 auto;
  padding: 0 15px;
}
@media (min-width: 480px) { .site-footer__inner { padding: 0 30px; } }
@media (min-width: 1000px) { .site-footer__inner { padding: 0 60px; } }

.site-footer a {
  color: var(--crs-text);
  font-weight: var(--crs-weight-bold);
  text-decoration: none;
}

/* The logo in a fixed 76px box, nudged 5px down inside it, as the vendor has
   it: the box, not the image, sets the row's height. */
.site-footer__logo {
  display: flex;
  align-items: center;
  width: 250px;
  height: 76px;
  margin: 0 auto 25px;
  overflow: hidden;
}
.site-footer__logo img {
  display: block;
  width: 250px;
  max-width: none;
  height: auto;
  transform: translateY(5px);
}

/* Mail above phone, each line 22px. */
.site-footer__contact { margin: 0 0 1.5em; }
.site-footer__contact-link {
  display: flex;
  justify-content: center;
  align-items: baseline;
  line-height: 1;
}
.site-footer__contact-link > svg,
.site-footer__contact-link span { display: block; line-height: 22px; }
.site-footer__contact-link span { padding-left: 10px; }

/* The -15px is the vendor's, an artefact of a float layout that the flex row
   kept; it shifts the pair 7px left of centre on a phone, and that is how the
   home page looks. */
.site-footer__social {
  display: flex;
  justify-content: center;
  margin: 0 0 0 -15px;
  padding: 0;
  list-style: none;
}
.site-footer__social li { margin-left: 5px; padding: 5px; line-height: 21px; }
.site-footer__social-link { display: block; }

.site-footer__icon { display: block; fill: currentColor; }
.site-footer__icon--email { width: 16px; height: 11px; }
.site-footer__icon--phone { width: 16px; height: 15px; }
.site-footer__icon--facebook { width: 7px; height: 16px; }
.site-footer__icon--instagram { width: 16px; height: 16px; }
.site-footer__social-link:hover,
.site-footer__social-link:focus-visible { color: var(--crs-red); }

.site-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 10px 24px;
  width: 100%;
  margin-top: 20px;
  padding-top: 20px;
  border-top: 0.5px solid rgb(0 0 0 / 0.5);
}
.site-footer__terms,
.site-footer__legal { display: block; line-height: 1; }
/* Dimmed, and full strength under the pointer, as the vendor's credit was. */
.site-footer__credit {
  display: block;
  padding-top: 3px;
  font-size: 12px;
  line-height: 18px;
  opacity: 0.4;
  transition: opacity 0.4s ease;
}
.site-footer__credit:hover,
.site-footer__credit:focus-visible { opacity: 1; }

@media (min-width: 768px) {
  .site-footer { text-align: left; }
  .site-footer__upper {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
  .site-footer__logo { margin: 0 0 30px; }
  .site-footer__contact-link,
  .site-footer__social { justify-content: left; }
  .site-footer__bottom { justify-content: flex-end; margin-top: 0; }
}
@media (min-width: 1000px) { .site-footer__contact { margin-bottom: 0; } }

@media (prefers-reduced-motion: reduce) {
  .site-footer__credit { transition: none; }
}

/* ---------- The terms page ----------

   CRS's own text, rendered unchanged from content/legal/. Only spacing here:
   the headings keep the site's one rule for size, weight and colour, and the
   paragraphs keep the reading measure every page uses. A section heading gets
   more room above it than a clause heading, so the twenty sections read as
   groups rather than as one list of ninety headings. */
.legal h2 { margin: var(--crs-space-xl) 0 var(--crs-space-s); }
.legal .legal-note + h2 { margin-top: 0; }
.legal h3 { margin: var(--crs-space-l) 0 var(--crs-space-xs); }
.legal p, .legal ul { margin: 0 0 var(--crs-space-s); }
/* Which language prevails: said once, above the text, and set apart from it. */
.legal .legal-note {
  margin-bottom: var(--crs-space-l);
  padding-left: var(--crs-space-s);
  border-left: 2px solid var(--crs-red);
  color: var(--crs-text-muted);
}

/* The privacy policy and the cookie statement (#203) also carry tables, the
   rider consent sentence as a quote, and the names of cookies. A table keeps
   its columns where there is room for them; on a phone each row becomes a small
   card, every cell under its own column name (the `data-label` legal-doc.js
   writes), because five columns at 390px is a table nobody can read. */
.legal blockquote {
  margin: 0 0 var(--crs-space-s);
  padding-left: var(--crs-space-s);
  border-left: 2px solid var(--crs-border);
}
.legal code {
  padding: 0 0.2em;
  border-radius: 3px;
  background: var(--crs-surface-raised);
  font-family: inherit;
  overflow-wrap: anywhere;
}
.legal .legal-table { max-width: 60rem; margin: 0 0 var(--crs-space-m); }
.legal table { width: 100%; border-collapse: collapse; }
.legal th,
.legal td {
  padding: var(--crs-space-xs) var(--crs-space-s) var(--crs-space-xs) 0;
  border-bottom: 1px solid var(--crs-border);
  text-align: left;
  vertical-align: top;
}
.legal th { font-weight: var(--crs-weight-bold); }
@media (max-width: 47.99rem) {
  .legal table, .legal tbody, .legal tr, .legal td { display: block; width: 100%; }
  .legal thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .legal tr { padding: var(--crs-space-xs) 0; border-bottom: 1px solid var(--crs-border); }
  .legal td { padding: var(--crs-space-2xs) 0; border-bottom: 0; }
  .legal td::before {
    content: attr(data-label);
    display: block;
    color: var(--crs-text-muted);
    font-size: var(--crs-step--1);
  }
  .legal td:empty { display: none; }
}

/* ---------- Full-bleed stages ---------- */

/* The thing that makes the existing home page look like itself: nine sections,
   each exactly one screen tall, each a photograph with a slanted red panel of
   text laid over it. Matching its colours and type without this produced pages
   that measured identical and still read as a different website -- which is
   what Jimmy saw.
   Copied from .fullsize--section and .slant-box, with two deliberate changes:
   no parallax (that needs GSAP), and the panel uses the vendor's OWN JS-free
   variant .slant-box--mobile -- a flat #de0b14 fill instead of ten .streak
   bars that start at width:0 and are wiped in on scroll. Same shape, visible
   without a line of script. */
.stage {
  position: relative;
  width: 100%;
  min-height: 100svh;
  overflow: clip;
  display: flex;
  align-items: center;
}


.stage__photo {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.stage__photo img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* A scrim, because the text sits on a photograph whose brightness we do not
   control. The existing site gets away without one because its panel is opaque
   red across the full height; ours is too, but the heading above it is not. */
.stage__photo::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to right, rgb(10 10 10 / 0.55), rgb(10 10 10 / 0.15));
}

 /* The panel is half the SECTION, not half a centred container. Getting that
    wrong put it at 27% of the screen -- a stripe rather than the half-page
    block the existing site uses, and the single thing that made the first
    version still not look like the home page. */
.stage__panel {
  position: relative;
  z-index: 1;
  background: var(--crs-red);
  color: var(--crs-text);
  width: 100%;
  padding: var(--crs-section-pad) var(--crs-gutter-mobile);
  /* A phone keeps the diagonal too -- the vendor's own mobile slant is 6vw.
     Without it the signature shape simply disappears on the screen most of
     this audience uses. */
  clip-path: polygon(0 6vw, 100% 0, 100% calc(100% - 6vw), 0 100%);
  margin-block: -6vw;
  /* Full height, as .slant-box is -- it is a half-page block, not a band
     across the top. Getting this wrong left the copy floating at the top of a
     photograph instead of anchoring half the screen. */
  align-self: stretch;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
/* The panel is 50% only once there is room for it. Between the breakpoint and
   a wide desktop it is 70%, because at 768px a 50% panel minus its gutters and
   its slant leaves a 172px copy box -- 21 characters a line, one paragraph
   running fourteen lines. Changing the slant's unit did not fix that: 24% of a
   panel that is itself 50% of the screen IS 12vw. Only the panel's own width
   moves it. The 600px cap on .stage__inner keeps the wide end honest. */
@media (min-width: 48rem) {
  /* margin-block back to 0 (#423). The -6vw above lets a phone's slanted top
     and bottom edges reach past the section; from here on the slant is on the
     SIDE edge, so it has nothing to do -- and it does harm. .stage clips, so
     6vw of the panel above the section and 6vw below it are invisible, and the
     60px section padding only covers that while 6vw <= 60px, i.e. below
     1000px. Above that the difference was cut out of the copy itself: on Bring
     Your Own Bike, whose panel is taller than a screen, the heading lost its
     first line and Logistics its last -- 26px at 1440, 55px at 1920.
     test/stage-clipping.test.js holds the arithmetic. */
  .stage__panel { max-width: 70%; padding-inline: var(--crs-gutter-mobile); margin-block: 0; }
  /* Mirrored per side, and on the vertical edge that meets the photograph.
     The extra padding on the slanted side keeps the copy off the diagonal's
     point -- without it the first line of every paragraph runs into it. */
  .stage--left .stage__panel {
    clip-path: polygon(0 0, 100% 0, 76% 100%, 0 100%);
    /* 16.8vw, NOT 24%. A percentage in `padding` resolves against the
       CONTAINING BLOCK -- here the full-width .stage -- not against the panel
       it is written on. The clip-path's 24% does resolve against the panel, so
       the two disagreed: the slant ate 24% of a 70%-wide panel while the
       padding reserved 24% of the whole viewport. Measured at 1440px: 405px of
       right padding where 173px was intended, leaving 254px of text -- 38
       characters a line, which is a newspaper column, not a page.
       16.8vw is 24% of a 70% panel; the 12vw below is 24% of a 50% one. */
    padding-right: calc(var(--crs-gutter) + 16.8vw);
  }
  .stage--right .stage__panel {
    margin-left: auto;
    clip-path: polygon(24% 0, 100% 0, 100% 100%, 0 100%);
    padding-left: calc(var(--crs-gutter) + 16.8vw);
    /* The vendor right-aligns copy in a right-hand panel, so the text edge
       follows the diagonal instead of fighting it. */
    text-align: right;
  }
  .stage--right .stage__inner { margin-left: auto; }
}

@media (min-width: 60rem) {
  .stage__panel { padding-inline: var(--crs-gutter); }
}
@media (min-width: 90rem) {
  .stage__panel { max-width: 50%; }
  /* The panel halves, so the slant's share of the viewport halves with it. */
  .stage--left .stage__panel { padding-right: calc(var(--crs-gutter) + 12vw); }
  .stage--right .stage__panel { padding-left: calc(var(--crs-gutter) + 12vw); }
}

/* Keeps the copy off the diagonal's point and from running the full half-width
   on a very wide screen, where a 1280px line is unreadable. */
.stage__inner { max-width: 600px; }
.stage--right .stage__inner { margin-left: auto; }

/* Inside the panel everything is white, including the heading -- red on red is
   the one place the site's own heading colour cannot apply. */
.stage__panel h2, .stage__panel h3 { color: var(--crs-text); margin-top: 0; }
.stage__panel p { color: var(--crs-text); }
.stage__panel p:last-child { margin-bottom: 0; }
.stage__panel .quote { border-left-color: var(--crs-text); }
.stage__panel a { color: var(--crs-text); }
.stage__panel .cta { background: var(--crs-text); color: var(--crs-red); }
.stage__panel .cta:hover { background: rgb(255 255 255 / 0.85); }

/* ---------- The panel wipes in: .slant-panel (#343) ----------

   The home page's slant box, measured in the browser on 2026-10-15: ten
   .streak bars stacked in a column over #d9d9d9 (six below 1000px, the first
   four hidden), each growing from the panel's outer edge to full width in 1s
   on cubic-bezier(0.22, 1, 0.36, 1), each with its own delay between 0 and 1s;
   the text rises 20px and fades in over 0.6s ease-in after 0.5s. It starts
   when the section's top reaches the middle of the screen, once.

   Rebuilt without GSAP, and inverted: the RESTING state is the finished panel,
   drawn by default -- bars at full width over the panel's own red -- so with no
   script, or reduced motion, nothing is hidden. Only `--armed` (set by
   slant-panels.js on a panel still below the trigger) hides; `--in` reveals.
   The bars scale rather than change width: same picture, no layout per frame.
   Everything is out of flow or a transform, so nothing moves the page. */
.slant-panel__bars {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: flex;
  flex-direction: column;
  pointer-events: none;
}
.slant-panel__bar {
  flex: 1 1 0;
  /* The vendor's own -1px: overlapping bars leave no hairline of grey. */
  margin-bottom: -1px;
  background: var(--crs-red);
  transform-origin: left center;
}
/* A right-hand panel's bars grow from the right edge, as the home page's do. */
.stage--right .slant-panel__bar { transform-origin: right center; }
.slant-panel > .stage__inner { position: relative; z-index: 1; }
@media (max-width: 62.49rem) {
  .slant-panel__bar:nth-child(-n+4) { display: none; }
}

/* Hidden, waiting for the trigger. Grey base, as on the home page. */
.slant-panel--armed { background: #d9d9d9; }
.slant-panel--armed .slant-panel__bar { transform: scaleX(0); }
.slant-panel--armed > .stage__inner { opacity: 0; transform: translateY(20px); }

.slant-panel--in .slant-panel__bar {
  animation: slant-bar 1s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.slant-panel--in > .stage__inner {
  animation: slant-text 0.6s ease-in 0.5s both;
}
/* The home page draws a fresh random delay per bar on every load. A fixed
   shuffle of the same 0-1s range gives the same broken-edge wipe without
   writing styles from script; a right-hand panel runs it backwards so two
   panels in a row do not wipe identically. */
.slant-panel__bar:nth-child(1)  { animation-delay: 0.13s; }
.slant-panel__bar:nth-child(2)  { animation-delay: 0.83s; }
.slant-panel__bar:nth-child(3)  { animation-delay: 0.29s; }
.slant-panel__bar:nth-child(4)  { animation-delay: 0.58s; }
.slant-panel__bar:nth-child(5)  { animation-delay: 0.07s; }
.slant-panel__bar:nth-child(6)  { animation-delay: 0.95s; }
.slant-panel__bar:nth-child(7)  { animation-delay: 0.41s; }
.slant-panel__bar:nth-child(8)  { animation-delay: 0.21s; }
.slant-panel__bar:nth-child(9)  { animation-delay: 0.69s; }
.slant-panel__bar:nth-child(10) { animation-delay: 0.5s; }
.stage--right .slant-panel__bar:nth-child(1)  { animation-delay: 0.5s; }
.stage--right .slant-panel__bar:nth-child(2)  { animation-delay: 0.69s; }
.stage--right .slant-panel__bar:nth-child(3)  { animation-delay: 0.21s; }
.stage--right .slant-panel__bar:nth-child(4)  { animation-delay: 0.41s; }
.stage--right .slant-panel__bar:nth-child(5)  { animation-delay: 0.95s; }
.stage--right .slant-panel__bar:nth-child(6)  { animation-delay: 0.07s; }
.stage--right .slant-panel__bar:nth-child(7)  { animation-delay: 0.58s; }
.stage--right .slant-panel__bar:nth-child(8)  { animation-delay: 0.29s; }
.stage--right .slant-panel__bar:nth-child(9)  { animation-delay: 0.83s; }
.stage--right .slant-panel__bar:nth-child(10) { animation-delay: 0.13s; }

@keyframes slant-bar {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}
@keyframes slant-text {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: none; }
}

/* The script does not arm anything under reduced motion; this holds even if a
   panel was armed before the setting changed. And a page printed before every
   panel has been scrolled past prints them all, red and readable. */
@media (prefers-reduced-motion: reduce), print {
  .slant-panel--armed { background: var(--crs-red); }
  .slant-panel--armed .slant-panel__bar,
  .slant-panel--armed > .stage__inner { opacity: 1; transform: none; animation: none; }
}

/* The panel is a fixed height stage, so long copy has to be able to scroll
   rather than overflow the clip. Rare, but a translation running long is
   exactly the case that would otherwise crop a sentence invisibly. */


/* ---------- Enquiry form (sleeper-unit hire) ---------- */

/* The first form the generated pages have ever carried. Styled from the same
   tokens as everything else rather than from a framework, because one form does
   not justify a dependency and because the site's inputs should look like the
   site. */
.enquiry { padding-block: var(--crs-section-pad); border-top: 1px solid var(--crs-border); }
.enquiry h2 { margin: 0 0 var(--crs-space-m); }
.enquiry__intro { max-width: 60ch; margin: 0 0 var(--crs-space-l); }

/* Off on the preview build. Marked out rather than hidden: a form that is not
   connected should say so where someone is about to type into it, not fail
   quietly after they have. */
.enquiry__disabled {
  max-width: 60ch;
  margin: 0 0 var(--crs-space-l);
  padding: var(--crs-space-s) var(--crs-space-m);
  border-left: 3px solid var(--crs-red);
  background: var(--crs-surface-raised);
}

/* The two answers to a send (#355). Hidden until the visitor is shown one: by `:target`
   when the form was posted without script and the endpoint redirected to #enquiry-sent
   or #enquiry-error, by `.is-visible` when the script did it inline. CSS hides them, not
   the `hidden` attribute, because `:target` can only reveal what a stylesheet hid. The
   error keeps the page's red; the thanks takes the neutral light edge so a success is
   not mistaken for a warning. */
.enquiry__status {
  display: none;
  max-width: 60ch;
  margin: 0 0 var(--crs-space-l);
  padding: var(--crs-space-s) var(--crs-space-m);
  border-left: 3px solid var(--crs-text);
  background: var(--crs-surface-raised);
}
.enquiry__status--error { border-left-color: var(--crs-red); }
.enquiry__status:target,
.enquiry__status.is-visible { display: block; }
.enquiry__status:focus { outline: none; }
.enquiry__status a { color: var(--crs-red-text); }
.enquiry__form[aria-busy="true"] button { cursor: progress; }
.enquiry__privacy a { color: var(--crs-red-text); }
.field input[aria-invalid="true"], .field textarea[aria-invalid="true"] {
  border-color: var(--crs-red-bright);
}

.enquiry__form { max-width: 44rem; }
.enquiry__form fieldset {
  margin: 0 0 var(--crs-space-l);
  padding: 0;
  border: 0;
}
.enquiry__form legend {
  padding: 0;
  margin-bottom: var(--crs-space-s);
  color: var(--crs-red-text);
  font-weight: var(--crs-weight-medium);
  text-transform: uppercase;
}
.fieldset__intro { margin: 0 0 var(--crs-space-s); color: var(--crs-text-muted); }

.field { margin: 0 0 var(--crs-space-m); }
.field label { display: block; margin-bottom: 0.35rem; font-weight: var(--crs-weight-medium); }
/* The required marker is ordinary text in the label, not a red asterisk with a
   legend elsewhere on the page: what the eye reads and what a screen reader
   announces should be the same words in the same place. */
.field__required { color: var(--crs-text-muted); font-weight: var(--crs-weight-light); }
.field__hint { display: block; margin-bottom: 0.35rem; font-size: var(--crs-step--1); color: var(--crs-text-muted); }

.field input, .field textarea {
  width: 100%;
  padding: 0.6rem var(--crs-space-s);
  background: var(--crs-surface);
  /* NOT --crs-border. That token is #3a3a3a, chosen for hairlines between
     sections, and against this page it measures 1.35:1 -- WCAG 1.4.11 asks 3:1
     for the boundary of a control, and at 390px the field boxes were barely
     findable. This is the only thing telling a visitor where to type. */
  border: 1px solid var(--crs-input-border);
  border-radius: var(--crs-radius);
  color: var(--crs-text);
  font-family: inherit;
  /* 16px, deliberately. Anything smaller makes iOS zoom the page on focus, and
     the visitor then has to pinch their way back out to see the rest of the
     form. */
  font-size: var(--crs-step-0);
}
.field textarea { resize: vertical; }
.field input:focus-visible, .field textarea:focus-visible {
  outline: 2px solid var(--crs-red-bright);
  outline-offset: 1px;
}

/* The honeypot. Off-screen rather than display:none, because some bots skip
   fields that are display:none and fill the ones that are merely positioned
   away -- which is exactly the behaviour we want to catch. */
.enquiry__hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.enquiry__privacy { max-width: 60ch; color: var(--crs-text-muted); font-size: var(--crs-step--1); }

/* A form that is switched off should LOOK switched off. It did not: the labels,
   the date pickers with their calendar icons and the text fields all looked
   live, so a visitor tapped a field, got no response, and had only a grey
   paragraph further up to explain it. Worse in a browser that ignores `inert`,
   where they can type the whole enquiry and find out at the Send button.

   Keyed on [inert] rather than a class, so the styling cannot drift from the
   behaviour -- one attribute drives both. */
.enquiry__form[inert] { opacity: 0.55; }
.enquiry__form[inert] .field input,
.enquiry__form[inert] .field textarea {
  background: transparent;
  border-style: dashed;
  cursor: not-allowed;
}
.enquiry__form button[disabled] { opacity: 0.5; cursor: not-allowed; }

/* A link out of a section to a related page -- today only the flagship's link
   to the hire page. Set apart from the body copy above it so it reads as a
   door rather than as the end of the paragraph. */
.aside-link { margin-top: var(--crs-space-l); }
/* `.prod p` zeroes the top margin above and outranks the rule above, which
   left the link under a photo strip touching the photographs. */
.photo-strip + .aside-link { margin-top: var(--crs-space-m); }
.aside-link a { font-weight: var(--crs-weight-medium); color: var(--crs-red-text); }

/* A strapline under the page title. Quieter than the lede below it and set in
   the red that stays legible at this size -- it is the line that carries the
   feeling, and the lede immediately after carries the facts, so the two must
   not compete. Italic because it is the one line on the page that is a claim
   about the experience rather than a statement about the product. */
.hero__tagline {
  margin: 0 0 var(--crs-space-s);
  color: var(--crs-red-text);
  font-size: var(--crs-step-1);
  font-style: italic;
  font-weight: var(--crs-weight-light);
}

/* The heartbeat line under the strapline: three beats that fade into a long
   calm, which is the strapline drawn rather than decorated.

   `currentColor` in the SVG and the colour set here, so the line and the words
   above it can never drift apart -- and so a future change of the red is one
   edit in tokens.css rather than one in a path nobody opens.

   Width is capped rather than fluid to the container: stretched across a wide
   hero the beats flatten out and it stops reading as a heartbeat at all. */
.heartbeat {
  display: block;
  width: 100%;
  max-width: 26rem;
  height: auto;
  margin: 0 0 var(--crs-space-m);
  color: var(--crs-red-text);
  /* Decorative and behind nothing: it must never sit on top of the text above
     it if a line wraps at an unlucky width. */
  pointer-events: none;
}


/* ---------- Line length ----------

   Every run of body copy on every page, capped at one measure.

   Written as ONE generic rule with named exceptions, not as a list of the
   selectors that happened to be wrong. The list version is how this survived
   three rounds of "make it match the home page": I capped the product pages,
   looked at the product pages, and called it done -- while an event page was
   still running prose at 152 characters a line. A rule that has to be extended
   by hand for every new block is a rule that is wrong again the next time
   somebody adds one.

   Found by measuring the rendered page against the home page rather than by
   comparing stylesheets, which is why it survived at all: the fonts, sizes,
   weights, line-heights and colours were already identical. What was not was
   how far a line is allowed to run. The home page never exceeds 76 characters;
   ours reached 200.

   Jimmy, three times: "Why isn't everything aligned yet with the homepage?"
   This is the part that was not. */
main :is(p, dd, li) { max-width: var(--crs-measure); }

/* The exceptions, each because the element is not a line of text:
   - photo rows hold images, and capping them shrinks the photographs;
   - the facts grid is already columns, and its cells are narrower than this.

   Form fields are NOT exempt, which was a second thought. I excused them first
   on the grounds that an input is a control rather than a line -- but the
   measurement then showed the field wrappers at 107 characters wide inside a
   44rem form, which is a 700px box to type a name into. A field is read before
   it is filled: the label, the hint and the answer all sit on the same line
   length as everything else now. */
.photo-strip li,
.gallery li,
.facts > div { max-width: none; }

/* ---------- Using the width without lengthening the line ----------

   Capping the measure makes a full-width band read well and look thin: 432px of
   text in a 1320px container leaves 900px of nothing. The home page never has
   that, because its text always sits in a half-width panel with a photograph in
   the other half -- which is what our `stage` sections do, and why those were
   never the problem.

   The plain bands have no such counterpart, so they use the width the other
   way: more columns, each one still capped at the measure. The line stays 65
   characters; the band stops looking half-abandoned. */
@media (min-width: 75rem) {
  /* Two newspaper columns, not a two-column grid. In a grid, five phases put
     the fifth alone on a row with an empty half beside it (Jimmy, 2026-10-14);
     letting it span the row changes nothing visible, because each item is
     capped at the measure. Columns balance by height instead, so the list ends
     level and reads down, then across, which is how a numbered sequence reads.
     The counter and the left padding are untouched. Left to itself the
     balancer split five as 2 + 3 and left the hole bottom-left, under the
     column that is read first; the break puts it 3 + 2, so the list ends
     ragged on the right, like a paragraph. Spec 5.2 fixes five phases. */
  .phases { columns: 2; column-gap: var(--crs-space-2xl); }
  .phases li { break-inside: avoid; }
  .phases li:nth-child(4):nth-last-child(2) { break-before: column; }
}

/* A list of labelled points is columns, not a single file. `auto-fit` with the
   measure as the MAXIMUM, so columns grow to a readable line and then split
   rather than stretching past it -- the previous 9rem minimum produced five
   columns of 38 characters on a wide screen, which is the same readability
   problem from the other direction. */
ul.facts {
  /* min(18rem, 100%), not 18rem (#423): a 320px phone leaves a stage panel
     280px of copy box, and a 288px minimum track pushed the Sleepliner page
     28px past the right edge of the screen. */
  grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), var(--crs-measure)));
  justify-content: start;
}

/* ---------- Less motion, when asked for ----------

   For someone who has told their device that motion makes them unwell, every
   transition and animation on the generated pages finishes at once, and nothing
   scrolls smoothly. Today that is one 120 ms arrow; the rule is universal so the next
   transition someone adds is covered without remembering this. Generated pages only:
   this file is not loaded on the mirrored ones. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
