/* Extracted from the page's inline <style> block.
   The site's Content-Security-Policy is style-src 'self', which blocks
   inline styles outright. Same rules, same order, now served as a file. */

/* ====================================================================
   PAGE BANNER
   Built from the same three layers as the homepage hero, recomposed so
   the two are recognisably related without being the same picture.
   Cost: 0 KB. No image request.
   ==================================================================== */

.page-banner {
  position: relative;
  display: flex;
  min-height: 400px;
  align-items: center;
  overflow: hidden;
  padding: calc(var(--header-h) + 34px) clamp(22px, 5vw, 82px) 54px;
  color: var(--white);

  /* Layer 1 — the charcoal wash, same 157deg axis as the hero.
     Layer 2 — the red bloom, moved to the left of frame, which is where
     the route leaves this banner rather than where it enters. */
  background:
    radial-gradient(900px 520px at 16% 88%, rgba(215, 25, 32, 0.17), transparent 62%),
    radial-gradient(760px 480px at 88% 8%, rgba(255, 255, 255, 0.05), transparent 62%),
    linear-gradient(157deg, #191d22 0%, #121519 46%, #0b0d0f 100%);
}

/* Layer 3 — the dot field. Mask runs on the opposite diagonal to the
   homepage, so the two pages read as a pair rather than a repeat. */
.page-banner::before {
  position: absolute;
  z-index: 0;
  inset: 0;
  content: "";
  background-image: radial-gradient(rgba(255, 255, 255, 0.16) 1px, transparent 1.4px);
  background-size: 26px 26px;
  -webkit-mask-image: linear-gradient(68deg, transparent 30%, #000 60%, transparent 96%);
  mask-image: linear-gradient(68deg, transparent 30%, #000 60%, transparent 96%);
  opacity: 0.5;
}

.page-banner__mark {
  position: absolute;
  z-index: 0;
  top: 74px;
  right: -150px;
  width: min(38vw, 520px);
  height: auto;
  opacity: 0.06;
}

.page-banner__content {
  position: relative;
  z-index: 3;
  width: min(680px, 60vw);
}

/* Breadcrumb keeps the homepage's service numbering visible, so a
   visitor landing here cold knows which of the three services this is. */
.page-banner__crumb {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 20px;
  color: var(--red-on-dark);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.115em;
  text-transform: uppercase;
}

.page-banner__crumb a {
  color: inherit;
  border-bottom: 1px solid rgba(255, 77, 84, 0.4);
  text-decoration: none;
}

.page-banner__crumb a:hover,
.page-banner__crumb a:focus-visible {
  border-bottom-color: currentColor;
}

.page-banner__crumb span[aria-hidden] {
  color: rgba(255, 255, 255, 0.34);
}

/* One step below the homepage hero's 50px, so the homepage keeps the top
   of the hierarchy while this still reads as a page headline. */
.page-banner h1 {
  max-width: 17ch;
  margin: 0;
  font-size: clamp(32px, 3.1vw, 46px);
  font-weight: 600;
  letter-spacing: -0.04em;
  line-height: 1.07;
  text-wrap: balance;
}

.page-banner__intro {
  max-width: 52ch;
  margin: 22px 0 0;
  color: var(--body-on-ink);
  font-size: clamp(16px, 1.05vw, 18px);
  line-height: 1.62;
}

/* --- The banner route -------------------------------------------------
   Enters from the top edge, turns once, and exits through the bottom
   into the page content, where the page rail picks it up. It reads as
   the homepage's journey continuing rather than restarting.

   preserveAspectRatio="none" stretches the geometry to the banner while
   vector-effect keeps the stroke at a true 4px, so it matches the
   homepage's uniform width at every viewport.
*/
/* The drawing area starts below the header, not at the top of the banner.
   Full-bleed, the entering segment ran straight through the navigation
   links at desktop widths. Offsetting the region is robust at every
   width, where nudging the path sideways would only miss the links until
   the nav wrapped or a label changed. */
.page-banner__route {
  position: absolute;
  z-index: 1;
  top: var(--header-h);
  left: 0;
  width: 100%;
  height: calc(100% - var(--header-h));
  pointer-events: none;
}

.route-path {
  fill: none;
  stroke: var(--red);
  stroke-width: var(--route-width);
  stroke-linecap: round;
}

/* The faint track showing the path ahead, same 13% as the homepage. */
.route-path--track {
  opacity: 0.13;
}

.page-banner__route .route-path--live {
  stroke-dasharray: 800;
  stroke-dashoffset: 800;
  animation: banner-draw 1500ms cubic-bezier(0.22, 0.61, 0.36, 1) 220ms forwards;
}

@keyframes banner-draw {
  to {
    stroke-dashoffset: 0;
  }
}

/* Drawn as an element rather than an SVG circle so the stretched
   viewBox cannot squash it into an ellipse. */
.route-node {
  position: absolute;
  z-index: 2;
  width: 20px;
  height: 20px;
  border: var(--route-width) solid var(--red);
  border-radius: 50%;
  transform: translate(-50%, -50%);
}

/* Node positions are percentages of the banner, but the route now only
   covers the part below the header, so the same fraction is taken of
   that shorter box to keep the node sitting on the line. */
.page-banner__node {
  top: calc(var(--header-h) + (100% - var(--header-h)) * 0.575);
  left: 59.7%;
  background: #121519;
  opacity: 0;
  animation: node-in 400ms ease 1250ms forwards;
}

@keyframes node-in {
  to {
    opacity: 1;
  }
}

/* ====================================================================
   THE PAGE RAIL
   The route does not stop at the banner. It crosses to the right gutter
   and runs the full height of the page, drawing as you scroll, with a
   node where the argument turns and a terminal node at the call to
   action. This is the page's main piece of motion.
   ==================================================================== */

.route-flow {
  /* Where the line stops, measured up from the bottom of the flow. The
     rail and the terminal node both read this, so the line ends exactly
     at the node's centre instead of running past it — the overshoot
     fixed on the homepage on 5 August. */
  --end-stop: 116px;

  position: relative;
}

/* The jog that carries the line from the banner's exit point (59.7%)
   across to the right gutter it then runs down. */
.route-flow__connector {
  position: absolute;
  z-index: 4;
  top: 0;
  left: 0;
  width: 100%;
  height: 130px;
  pointer-events: none;
}

.route-flow__connector .route-path--live {
  stroke-dasharray: 620;
  stroke-dashoffset: 620;
  animation: banner-draw 900ms cubic-bezier(0.22, 0.61, 0.36, 1) 1450ms forwards;
}

/* The rail itself. Sits at 5% from the right, which is where the
   connector hands over. */
.route-flow__rail,
.route-flow__rail-track {
  position: absolute;
  z-index: 4;
  top: 126px;
  right: 5%;
  bottom: var(--end-stop);
  width: var(--route-width);
  border-radius: 2px;
  background: var(--red);
  transform: translateX(50%);
  pointer-events: none;
}

.route-flow__rail-track {
  opacity: 0.13;
}

/*
   Scroll-linked drawing with no JavaScript at all, using a scroll-driven
   animation. Everything is drawn and visible by default; the animated
   state is opted into only where the browser supports it. That is the
   same lesson as the reveal bug in the 5 August changelog — never leave
   content depending on something that might not run.
*/
@supports (animation-timeline: scroll()) {
  .route-flow__rail {
    transform-origin: top;
    scale: 1 0;
    animation: rail-draw linear forwards;
    animation-timeline: scroll(root block);
    animation-range: 22% 88%;
  }

  @keyframes rail-draw {
    to {
      scale: 1 1;
    }
  }
}

.route-flow__node {
  z-index: 5;
  right: 5%;
  background: var(--paper);
  transform: translate(50%, -50%);
}

.route-flow__node--turn {
  top: 0;
}

/* translateY(+50%) so the node's centre lands exactly on --end-stop,
   which is where the rail above it terminates. */
.route-flow__node--end {
  bottom: var(--end-stop);
  background: var(--ink);
  transform: translate(50%, 50%);
}

/* Below 861px the homepage simplifies its route; below 721px it hides it
   because it would cross the reading column. Same thresholds here. */
@media (max-width: 860px) {
  .page-banner__route,
  .page-banner__node,
  .route-flow__connector,
  .route-flow__rail,
  .route-flow__rail-track,
  .route-flow__node {
    opacity: 0.5;
  }
}

@media (max-width: 720px) {
  .page-banner {
    min-height: 0;
  }

  .page-banner__content {
    width: 100%;
  }

  .page-banner__route,
  .page-banner__mark,
  .route-flow__connector,
  .route-flow__rail,
  .route-flow__rail-track,
  .route-node {
    display: none;
  }
}

/* ====================================================================
   CONTENT
   Copy is PROVISIONAL and not signed off.
   ==================================================================== */

.mock-section {
  position: relative;
  padding: 84px clamp(22px, 5vw, 82px);
  background: var(--paper);
}

.mock-section--white {
  background: var(--white);
}

.mock-section--lede {
  padding-top: 96px;
}

.mock-lede {
  max-width: 62ch;
  margin: 0;
  color: var(--body-on-paper);
  font-size: clamp(17px, 1.2vw, 20px);
  line-height: 1.66;
}

.mock-section h2 {
  max-width: 20ch;
  margin: 0 0 42px;
  font-size: clamp(27px, 2.6vw, 38px);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.14;
}

.mock-grid {
  display: grid;
  max-width: 1180px;
  gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  margin: 0;
  padding: 0;
  list-style: none;
}

/*
   Hover lift, matching the homepage service rows. Uses `translate`
   rather than `transform`, because the reveal animation's settled state
   sets `transform` and the two would otherwise fight — exactly the bug
   fixed on 5 August. :focus-within gets the same treatment so keyboard
   users see it too.
*/
.mock-card {
  padding: 30px 28px 34px;
  border: 1px solid transparent;
  background: transparent;
  translate: 0 0;
  transition:
    translate 260ms ease,
    box-shadow 260ms ease,
    background-color 260ms ease,
    border-color 260ms ease;
}

.mock-card:hover,
.mock-card:focus-within {
  border-color: var(--line);
  background: var(--white);
  box-shadow:
    0 2px 6px rgba(17, 19, 22, 0.05),
    0 18px 38px rgba(17, 19, 22, 0.09);
  translate: 0 -5px;
}

.mock-section--white .mock-card:hover,
.mock-section--white .mock-card:focus-within {
  background: var(--paper);
}

.mock-card:hover .mock-card__number,
.mock-card:focus-within .mock-card__number {
  color: var(--red-text);
}

.mock-card h3 {
  margin: 18px 0 10px;
  font-size: clamp(19px, 1.5vw, 23px);
  font-weight: 600;
  letter-spacing: -0.02em;
}

.mock-card p {
  margin: 0;
  color: var(--body-on-paper);
  font-size: 16px;
  line-height: 1.64;
}

.mock-card__number {
  display: block;
  color: var(--muted-number);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.115em;
  transition: color 260ms ease;
}

/* Grows out from the left as the card arrives. */
.mock-card__rule {
  display: block;
  width: 34px;
  height: 3px;
  margin-top: 12px;
  background: var(--red);
  transform-origin: left;
}

/* --- The two-column block with the photograph ------------------------- */

.mock-split {
  display: grid;
  max-width: 1180px;
  align-items: center;
  gap: clamp(34px, 5vw, 74px);
  grid-template-columns: 1fr;
}

@media (min-width: 921px) {
  .mock-split {
    grid-template-columns: 1fr minmax(320px, 560px);
  }
}

.mock-split h2 {
  margin-bottom: 22px;
}

.mock-figure {
  position: relative;
  margin: 0;
}

.mock-figure img {
  display: block;
  width: 100%;
  height: auto;
}

/* A red bracket on the corner, tying the photograph to the route rather
   than letting it sit as a foreign object on the page. */
.mock-figure::after {
  position: absolute;
  z-index: 2;
  bottom: -14px;
  left: -14px;
  width: 96px;
  height: 96px;
  border-bottom: var(--route-width) solid var(--red);
  border-left: var(--route-width) solid var(--red);
  content: "";
}

.mock-figure figcaption {
  margin-top: 14px;
  color: var(--body-on-paper);
  font-size: 13px;
  line-height: 1.5;
}

/* --- Call to action --------------------------------------------------- */

.mock-cta {
  position: relative;
  padding: 88px clamp(22px, 5vw, 82px);
  background: var(--ink);
  color: var(--white);
}

.mock-cta h2 {
  max-width: 18ch;
  margin: 0 0 14px;
  font-size: clamp(25px, 2.2vw, 33px);
  font-weight: 600;
  letter-spacing: -0.03em;
}

.mock-cta p {
  max-width: 54ch;
  margin: 0 0 30px;
  color: var(--body-on-ink);
  line-height: 1.62;
}

/* ====================================================================
   ENTRANCE MOTION
   Sections and cards fade and rise once as they arrive, and the rules
   and photograph settle with them. Driven entirely by view() timelines,
   so there is no scroll handler and no JavaScript.

   Note the direction of the @supports: content is visible by default
   and the hidden starting state is opted into only where the animation
   can actually run.
   ==================================================================== */

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    [data-rise] {
      opacity: 0;
      translate: 0 22px;
      animation: rise 620ms cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
      animation-timeline: view();
      animation-range: entry 8% cover 26%;
    }

    .mock-card__rule {
      scale: 0 1;
      animation: rule-grow 520ms cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
      animation-timeline: view();
      animation-range: entry 12% cover 30%;
    }

    .mock-figure img {
      opacity: 0;
      scale: 1.05;
      animation: figure-settle 900ms cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
      animation-timeline: view();
      animation-range: entry 4% cover 34%;
    }

    .mock-figure::after {
      opacity: 0;
      animation: node-in 500ms ease forwards;
      animation-timeline: view();
      animation-range: entry 30% cover 46%;
    }
  }
}

@keyframes rise {
  to {
    opacity: 1;
    translate: 0 0;
  }
}

@keyframes rule-grow {
  to {
    scale: 1 1;
  }
}

@keyframes figure-settle {
  to {
    opacity: 1;
    scale: 1;
  }
}

/* Staggered so the three cards arrive as a sequence, not a block. */
.mock-card:nth-child(2) {
  animation-delay: 90ms;
}

.mock-card:nth-child(3) {
  animation-delay: 180ms;
}

/* ====================================================================
   REDUCED MOTION — everything drawn, settled and still.
   ==================================================================== */

@media (prefers-reduced-motion: reduce) {
  .page-banner__route .route-path--live,
  .route-flow__connector .route-path--live {
    stroke-dashoffset: 0;
    animation: none;
  }

  .page-banner__node,
  .mock-figure::after {
    opacity: 1;
    animation: none;
  }

  .route-flow__rail {
    scale: 1 1;
    animation: none;
  }

  [data-rise],
  .mock-figure img {
    opacity: 1;
    translate: 0 0;
    scale: 1;
    animation: none;
  }

  .mock-card__rule {
    scale: 1 1;
    animation: none;
  }

  .mock-card:hover,
  .mock-card:focus-within {
    translate: 0 0;
  }
}

/* Mockup-only notice, sitting at the end of the document in normal flow.
   It was pinned to the bottom of the viewport at first, which laid a 45px
   amber bar across the screen at z-index 90. The red rail runs underneath
   at z-index 4, and because the drawn end of the line tracks the scroll
   position, that end spent most of its time hidden behind the bar. It
   read as the line being cut off. */
.mock-flag {
  margin: 0;
  padding: 11px clamp(22px, 5vw, 82px);
  border-top: 2px solid #b58900;
  background: #3a2c00;
  color: #ffd97a;
  font-size: 13px;
  line-height: 1.5;
}
