/* Home page only. Loaded after site.css. */

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

/*
 * The band is exactly as tall as what is in it, and no taller.
 *
 * It used to carry a height of its own - a share of its width, so that the film
 * ran at something near its 4:3 - and the trouble with that is that the film
 * kept growing after the content stopped. On a tall wide display the ratio
 * asked for 1382px against about 960px of carousel and cards, so the band ended
 * with hundreds of pixels of empty film hanging below the last card.
 *
 * So no height here at all. The 80px under the cards is the only slack in it,
 * and it lives on .hero__audiences where it can be read as what it is. Wider
 * displays still get more film, they just get it sideways: the band keeps its
 * height and `object-fit: cover` fills the extra width from the same frame.
 */
.hero {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  background: var(--navy-900);
  color: #fff;
}

.hero__media,
.hero__scrim,
.hero__glow {
  position: absolute;
  inset: 0;
}

/*
 * Where the band's crop is taken from the film: 75px off the top, the rest off
 * the bottom, so what you see always starts just under the top of the frame.
 *
 * `max(-75px, 100%)` rather than a bare `-75px`, and the second term is the
 * safety.
 *
 * `object-position` offsets the frame against the band, and a percentage
 * resolves against the overflow the crop actually has - so `100%` here IS the
 * overflow, as a negative number, and it is the furthest the frame can move
 * before its own bottom edge comes into view. `max()` of the two takes whichever
 * is less negative: the flat 75px wherever there is at least that much overflow
 * to spend, and the overflow itself wherever there is not.
 *
 * Without the second term this breaks, and it broke: a bare `-100px` was here,
 * and at every width where the band was close to the film's own 4:3 there was
 * less than 100px of overflow, so the frame slid off its own bottom edge and
 * left a strip of bare navy under it. Those were the seams at certain widths.
 * A fixed offset cannot know how much room it has. This one asks.
 */
.hero__media {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center max(-75px, 100%);
  display: block;
}

/* Full strength. It was at 0.55 over the navy, which with the scrim on top of
   it meant the film was barely visible. */
.hero__video {
  opacity: 1;
}

/* One background treatment behind every slide: the film, then a scrim, then the
 * blue glow. Nothing keys off the slide.
 *
 * 0.15 is the baseline, and it is what most of the frame gets - the film now
 * comes through at full strength across the right two thirds.
 *
 * It cannot be 0.15 everywhere. Measured against the frames this video actually
 * shows, over the box the copy occupies, a flat 15% left 31% of that area below
 * 4.5:1 and the worst of it at 1.74:1. White text on a sunlit shot is simply not
 * readable. So the tint ramps back up behind the words and nowhere else: 0.72 at
 * the left edge, down to the 0.15 baseline by 58%, which is past the copy. */
.hero__scrim {
  background: linear-gradient(
    90deg,
    rgba(20, 33, 58, 0.72) 0%,
    rgba(20, 33, 58, 0.5) 30%,
    rgba(20, 33, 58, 0.15) 58%,
    rgba(20, 33, 58, 0.15) 100%
  );
}

/*
 * Not decoration. Sampled over the film in the region the copy occupies, the
 * brightest patches leave white text at 2.67:1 - about half a percent of the
 * area, but a headline only has to land on one of them. The shadow is what
 * keeps those readable now the scrim no longer covers them.
 *
 * On the text, not on .hero__inner: the buttons carry their own solid fills and
 * a shadow on those labels reads as a printing fault.
 */
.hero .h1,
.hero .eyebrow,
.hero__lede,
.hero__note,
.hero .hero-tagline {
  text-shadow:
    0 1px 2px rgba(10, 16, 30, 0.55),
    0 0 16px rgba(10, 16, 30, 0.4);
}

.hero__glow {
  background: radial-gradient(
    120% 90% at 18% 0%,
    rgba(138, 162, 197, 0.35) 0%,
    rgba(20, 33, 58, 0) 62%
  );
}

/* Sized by its tallest slide, never by the hero. Letting it take the slack
   pushed the dots to the bottom of the film, a long way under the copy they
   belong to, and pushed the cards down with them. The slack falls below the
   cards instead, where it is just more of the frame. */
/*
 * The top pad scales rather than sitting at a fixed 130px.
 *
 * 9vw is 130px at 1440, so the wide layout is unchanged, and it comes down with
 * the viewport from there. It had to: below 1080 the cards go two-up and the
 * band gets taller on its own, so a flat 130px of air on top was pushing the
 * copy most of the way down a tablet screen before a word of it was readable.
 */
.hero__stage {
  position: relative;
  flex: 0 0 auto;
  padding-top: clamp(40px, 9vw, 130px);
  overflow: hidden;
}

.hero__slide {
  flex: 0 0 100%;
  min-width: 0;
}

.hero__inner {
  max-width: var(--page-max);
  margin: 0 auto;
  /* The bottom pad clears the dots and nothing else. It used to carry the gap
     to the next section too; the cards do that now.

     The top tier is what sets the copy down off the band's top edge. It was
     128px before the band was shortened and 32px straight after, which put the
     eyebrow hard against the edge. */
  padding: clamp(48px, 6vw, 80px) 32px clamp(54px, 4.5vw, 60px);
}

/* The hero slide's column, holding the eyebrow, title, lede and actions. A
   layout width, not a text measure: the lede inside it carries its own. */
.hero__col {
  max-width: 760px; /* not-a-measure: the slide column, not its copy */
  display: grid;
  gap: 24px;
}

/* The Fix It slide: eyebrow across the top, logo beside its copy, button
   underneath. The mark is tall and stacked, so setting it beside the text
   rather than above it keeps the slide from running past the fold. */
.hero__col--wide {
  max-width: 1040px;
}

.hero__split {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 40px;
  /* Top-aligned, not centred: the copy column is taller than the mark, so
     centring drops the logo 14px below the first line of text and the two
     columns stop sharing a top edge with the eyebrow above them. */
  align-items: start;
}

.hero__split-body {
  display: grid;
  gap: 20px;
}

/* One column before the text gets too narrow to read beside a 300px mark. */
@media (max-width: 900px) {
  .hero__split {
    grid-template-columns: 1fr;
    gap: 24px;
  }
}

.hero__lede {
  font-size: 20px;
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.86);
  max-width: 660px;
  text-wrap: pretty;
}

/* The one line on a slide that should land harder than the lede above it.
   Heavier and full white rather than the lede's 86%, so it reads as the point
   rather than as more of the paragraph. Mirrors how the Fix It campaign sets
   the same sentence on its own site. */
.hero__note {
  font-size: 18px;
  font-weight: 600;
  line-height: 1.5;
  color: #fff;
  max-width: 660px;
  text-wrap: pretty;
}

.hero__dots {
  position: absolute;
  left: 0;
  right: 0;

  /* 6px, not 20: the stage's bottom padding came down with the band, and at
     20px the visible dot ended up level with the last line of copy. */
  bottom: 6px;
}

/*
 * No dots on mobile: the slide is swipeable, and the dots are what is left
 * over from a layout that had room for them.
 *
 * Keyed off the attribute home.js sets from isCompact(), the same predicate the
 * testimonial rail's page size comes from, so both carousels shed their paging
 * furniture at exactly the same width. This was a `(max-width: 759px)` query
 * and that was off by one: at an innerWidth of 759 the query is false, so the
 * hero kept its dots at the one width where the rail below had already dropped
 * its own. See COMPACT_MAX in home.js.
 */
.hero[data-compact] .hero__dots {
  display: none;
}

/* ------------------------------------------- audience cards, over the film */

/* Bottom of the hero rather than a band of their own. `position: relative` is
   what lifts them above the film and the scrim, both of which are absolutely
   positioned siblings earlier in the source. */
/* The top pad is the gap to the dots. The cards are a different thing from the
   slide above them, and butted up under the dot row they read as part of it. */
.hero__audiences {
  position: relative;
  flex: 0 0 auto;
  padding-top: 32px;
  padding-bottom: 80px;
}

/*
 * The row itself, and the base treatment: a plain white card on a light band,
 * which is what it looks like once home.js moves it out of the film. Everything
 * that inverts it is scoped under .hero__audiences below, so the two states are
 * the same markup reading its surroundings rather than two sets of markup.
 *
 * Four, then two by two, then one. Explicit counts rather than `auto-fit`,
 * because auto-fit fits as many as will go and that includes three - which
 * leaves a four-item row as 3 + 1, one card orphaned on its own line under a
 * full one. Two by two is the only balanced way to break four.
 *
 * The single-column step is also where home.js lifts the row out of the film
 * and into the band below, so BREAKPOINT_STACK there has to match the 639px
 * here. They are one decision written in two languages.
 */
.audiences {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 32px;
}

@media (max-width: 1079px) {
  .audiences {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 24px;
  }
}

@media (max-width: 639px) {
  .audiences {
    grid-template-columns: minmax(0, 1fr);
    gap: 16px;
  }
}

/*
 * `:where()` on the parent, deliberately. These are the base treatment and the
 * .hero__audiences rules below are meant to beat them, but `.audiences .card`
 * and `.hero__audiences .card` are both (0,2,0) - a tie, settled by whichever
 * sits lower in the file. That is exactly how every card came to hover blue:
 * the base hover rule was below, so its border-color won and no amount of
 * correctness in the hero rule could show. `:where()` contributes nothing to
 * specificity, so the base is (0,1,0), the override is (0,2,0), and the
 * question stops depending on line numbers.
 *
 * The color is on the anchor so any text in a card that is not in a .body or a
 * .card__title reads as copy rather than as a link.
 */
:where(.audiences) .card {
  display: grid;
  align-content: start;
  gap: 12px;
  color: var(--text-body);
}

:where(.audiences) .card:hover {
  border-color: var(--primary);
  box-shadow: var(--shadow-card-hover);
  text-decoration: none;
}

/*
 * Inverted and translucent, so the frame reads through the cards rather than
 * being blocked out by four rectangles.
 *
 * The blur is not decoration: it flattens the frame's local contrast so a hard
 * edge in the film cannot run through a line of text. It is also what makes the
 * fill work at all - 20% black over raw film would leave white text sitting on
 * whatever highlight happened to be under it.
 *
 * Sampled at twenty-four points across the 32.6s loop over all four cards, 96
 * readings, white on the blurred composite bottoms out at 3.65:1. Two of the 96
 * land under 4.5:1, both on the Assessors card and both on the same sunlit
 * patch the film passes through around t=5.4s and t=10.9s. It is position, not
 * hue: plain black measured 4.14:1 in the same place.
 *
 * The fill alpha is the lever, and it is expensive. Measured: 0.24 leaves one
 * failure, 0.28 one, 0.32 one, and 0.36 is the first value that clears AA on
 * every reading - nearly double the fill, and visibly darker. Left at 0.20 by
 * decision; raise --card-alpha if those two seconds need to pass.
 */
/*
 * Each card carries one of the four categorical colors, as an RGB triplet so
 * the same value can be spent at two different alphas: quiet at rest, and
 * strong enough on hover to read as that color rather than as tinted film.
 *
 * The triplets are the tokens taken down to roughly a third of their lightness.
 * At 0.2 over film a full-strength --primary or --sam washes out to a pastel
 * haze; darkened, the same hue reads as a shade and the white ink keeps its
 * ground. --card-accent is the same hue at full strength, and on hover it is
 * both the outline and the glow: one saturated edge reading against the shade
 * inside it, which is where the color belongs.
 */
.hero__audiences .card {
  /* Neutral fallback, so a card without a color modifier is still legible. */
  --card-tint: 0 0 0;
  --card-accent: 255 255 255;
  --card-alpha: 0.2;

  /* Icon and title share the top row, body spans underneath. Stacked, the 30px
     icon and the 13px label read as two separate things; side by side they read
     as one heading with a mark on it. */
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  column-gap: 12px;
  padding: 22px;
  background: rgb(var(--card-tint) / var(--card-alpha));
  border-color: rgba(255, 255, 255, 0.25);
  backdrop-filter: blur(20px) saturate(1.1);
  box-shadow: 0 10px 30px rgba(10, 16, 30, 0.35);
  transition:
    background-color var(--dur-micro) ease,
    border-color var(--dur-micro) ease,
    box-shadow var(--dur-micro) ease;
}

/* Four distinct colors, not four destinations. They matched the accent of the
   page each card links to while every page ran its own; with the site back on
   one blue there is nothing left to match, so they are a categorical set
   again. Where to Start is fuchsia everywhere on the site and the Shield
   Program's first tier is Blue, which spoke for two of the four. */
.hero__audiences .card--fuchsia {
  --card-tint: 74 22 58; /* --sam #ab4289 */
  --card-accent: 171 66 137;
}

.hero__audiences .card--teal {
  --card-tint: 0 51 56; /* --teal #006d77 */
  --card-accent: 0 138 150;
}

.hero__audiences .card--blue {
  --card-tint: 11 58 104; /* --primary #1976d2 */
  --card-accent: 25 118 210;
}

.hero__audiences .card--orange {
  --card-tint: 84 36 12; /* --orange #be541c */
  --card-accent: 190 84 28;
}

.hero__audiences .card__icon {
  grid-area: 1 / 1;
}

.hero__audiences .card__title {
  grid-area: 1 / 2;
}

/* Back at the card's left edge rather than indented under the title, so the
   paragraph keeps a straight left margin with everything else in the card. */
.hero__audiences .card .body {
  grid-column: 1 / -1;
}

/* Full white, not .on-dark's 86%: there is no contrast to spare here. */
.hero__audiences .card,
.hero__audiences .card .body,
.hero__audiences .card__title,
.hero__audiences .card__icon {
  color: #fff;
}

/* The fill comes up to 0.72, which is where the hue stops being a tint and
   starts being the card's color. The outline is that same hue at full opacity,
   and the glow is it again thrown outward. Two shadows: the cast shadow keeps
   the card sitting on the film, the second is the glow itself, with no offset
   so it reads as light rather than as depth. */
.hero__audiences .card:hover {
  background: rgb(var(--card-tint) / 0.72);
  border-color: rgb(var(--card-accent));

  /* The whole card is the link. Without this the site-wide a:hover underline
     lands on the title, which reads as one word being a link inside a tile that
     already is one. */
  text-decoration: none;
  box-shadow:
    0 10px 30px rgba(10, 16, 30, 0.45),
    0 0 28px rgb(var(--card-accent) / 0.55);
}

/* The color change is the hover state's whole point, so it must not be the only
   signal for anyone who has asked the system to stop things moving. The border
   and the glow still switch; only the easing goes. */
@media (prefers-reduced-motion: reduce) {
  .hero__audiences .card {
    transition: none;
  }
}

/* The 44px hit area is built into .carousel__dot in site.css. */

@media (prefers-reduced-motion: reduce) {
  .hero__video {
    display: none;
  }
}

@media (max-width: 1000px) {
  .hero .h1--hero {
    font-size: 40px;
  }
}

@media (max-width: 640px) {
  .hero__inner {
    padding: clamp(40px, 9vw, 56px) 20px clamp(50px, 7vw, 56px);
  }

  .hero__audiences {
    padding-top: 24px;
    padding-bottom: 56px;
  }

  .hero__lede {
    font-size: 17px;
  }
}

/* --------------------------------------------------------- testimonials */

/* Sits under the section heading; one step down from .body-lg. */
.quotes__sub {
  font-size: 16px;
  line-height: 1.7;
  color: var(--text-secondary);
}

.quotes__h2 {
  font-size: 38px;
  line-height: 1.1;
}

/* Outer element carries the padding; the inner one carries overflow. Combining
 * them would clip at the padding box and bleed neighbouring cards. The rail sets
 * no top padding: the 24px below the header block comes from .section__head. */
.quotes__rail {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 32px 24px;
  max-width: var(--page-max);
  margin: 0 auto;
}

/* Scrolls horizontally by default so all nineteen cards stay reachable with
 * JS off. Once home.js has paginated the track it switches to hidden and the
 * carousel takes over. */
.quotes__viewport {
  flex: 1 1 auto;
  min-width: 0;
  overflow-x: auto;
}

/*
 * The last page is usually short - nineteen quotes at three a page leaves one -
 * and a short page still has to look like the pages around it.
 *
 * So the column width is derived from the page size rather than from the number
 * of cards actually on this page: each card is the width it would have if the
 * page were full, and `justify-content: center` centers however many there are.
 * One card lands in the middle, two sit either side of the middle a normal gap
 * apart. `repeat(--page-items, 1fr)` instead would have stretched the lone card
 * across the whole rail, and `repeat(--page-cols, 1fr)` left it hard against the
 * left edge with two columns of air beside it.
 *
 * A full page falls out of the same formula unchanged, so there is no special
 * case: items equals cols, the width is a third, and centering has no slack to
 * work with. home.js sets both counts.
 */
.quotes__page {
  --quotes-gap: 24px;
  --page-cols: 1;
  --page-items: 1;

  flex: 0 0 100%;
  min-width: 0;
  display: grid;
  grid-template-columns: repeat(
    var(--page-items),
    minmax(0, calc((100% - (var(--page-cols) - 1) * var(--quotes-gap)) / var(--page-cols)))
  );
  justify-content: center;
  gap: var(--quotes-gap);
  align-items: stretch;
}

/* Three explicit rows - mark, quote, attribution - so the middle row absorbs
 * the slack. With align-content:space-between a short quote pushed its
 * attribution up and the row of cards read as ragged. */
.quote {
  display: grid;
  grid-template-rows: auto 1fr auto;
  gap: 20px;
  min-width: 0;
  padding: 32px;
  background: var(--grey-100);
  border: 1px solid var(--border-default);
  border-radius: var(--radius);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
}

.quote__mark {
  font-size: 28px;
  color: var(--gold-500);
}

.quote__text {
  font-size: 16px;
  line-height: 1.62;
  color: var(--text-body);
  text-wrap: pretty;
}

.quote__who {
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--navy-800);
  border-top: 1px solid var(--border-subtle);
  padding-top: 18px;
  align-self: end;
}

.quotes__dots {
  padding: 0 32px;
  gap: 12px;
  flex-wrap: wrap;
}

/* Before pagination the cards are one flat row; give them a sensible width. */
.quotes__track > .quote {
  flex: 0 0 min(360px, 80vw);
}

/*
 * No arrows once the rail is one card per page - the mobile layout, where the
 * card fills the width and the gesture for it is a drag. Two 40px buttons
 * either side of a single card take width the card wants and duplicate the
 * dots underneath.
 *
 * Keyed off the attribute home.js sets from the page size it just used, NOT a
 * media query at the matching width. Those two do not agree at the boundary: at
 * an innerWidth of 759, which perPageFor() reads as one card, the CSS query
 * (max-width: 759px) evaluates false, so the arrows stayed while the rail was
 * already 1-up. One breakpoint decision, made once, in one place.
 */
.quotes[data-per-page='1'] .carousel__arrow,
.quotes[data-per-page='1'] .quotes__dots {
  display: none;
}

@media (max-width: 640px) {
  .quotes__rail {
    padding: 0 20px 24px;
  }

  .quotes__dots {
    padding: 0 20px;
  }

  .quotes__h2 {
    font-size: 30px;
  }
}

/* ------------------------------------------------------- best practices */

.bp {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: 64px;
  align-items: center;
}

/* Full white, like the rest of the blue band. At 90% it measured 4.03:1, which
   passes only because 24px counts as large text; there is no room on this
   background for a tint that also has to work at body size. */
.bp__lede {
  font-size: 24px;
  font-weight: 300;
  line-height: 1.5;
  color: #fff;
  max-width: 680px;
  text-wrap: pretty;
}

.bp__download,
.bp__download:hover {
  background: #fff;
  color: var(--navy-800);
  border-color: #fff;
  padding: 14px 22px;
  gap: 12px;
}

.bp__download:hover {
  background: var(--grey-200);
  border-color: var(--grey-200);
}

.bp__badge-cell {
  display: grid;
  place-items: center;
}

/*
 * No panel behind it. The shield used to sit on a padded --grey-200 square
 * with a shadow, because the artwork was the mark on transparency and its
 * navy outline had nothing to stand on against the band.
 *
 * The white is in the artwork now, filled to the shield's own outline rather
 * than to a square, so the mark reads on the blue by itself and the panel is
 * a box around a shape that no longer needs one.
 *
 * 200px against a 370x400 file, so the badge still has 2x to draw from.
 */
.bp__badge {
  width: 100%;
  max-width: 200px;
  display: block;
}

/*
 * Stacked, everything centres.
 *
 * Side by side the copy is a column with the badge beside it, so it reads from
 * a left edge. Once the badge drops underneath, that edge is holding one thing
 * and the badge - centred in its own cell - is holding another, which is the
 * split visible at 1000px and down: ranged-left text over a centred shield.
 *
 * In the same query that stacks the grid, deliberately. Two queries at the
 * same number drift the moment one of them is edited, and there is no width
 * where one of these is right without the other.
 *
 * .stack is a grid, so text-align alone would leave its children full-width
 * and only centre the text inside them; justify-items centres the boxes.
 */
@media (max-width: 1000px) {
  .bp {
    /* minmax(0, …), not a bare 1fr. A bare 1fr keeps min-width:auto, so the
       max-content row below would raise this column's floor to its own 519px
       and the band would overflow the page rather than wrap. */
    grid-template-columns: minmax(0, 1fr);
    gap: 40px;
    text-align: center;
  }

  .bp .stack {
    justify-items: center;
  }
}

/*
 * Equal width once the two buttons stack, and no breakpoint to keep in sync.
 *
 * Resources is 149px and Provide Feedback is 354px, so wrapped they were a
 * short button over a long one. The pair wants to be one width on one line
 * and another width on two, which is normally a media query - but the width
 * it wraps at is decided by the labels, measured at 559px today. The last
 * number in here that described its own content rather than tracking it went
 * stale the moment the copy moved, so this tracks it instead.
 *
 * width:max-content sizes the row to the single line it would rather be -
 * 149 + 16 + 354 = 519px. On one line there is no free space, so flex-grow
 * has nothing to hand out and both keep their natural widths. Narrower than
 * that, max-width:100% clamps the row to the column, each button becomes the
 * only item on its flex line, and grow fills that line - so they come out
 * identical without either number appearing here.
 */
.bp .stack {
  /* Same reason as the column above: .stack is a grid, and its implicit column
     would otherwise be floored at the row's max-content width. */
  grid-template-columns: minmax(0, 1fr);
}

.bp .btn-row {
  width: max-content;
  max-width: 100%;
}

.bp .btn-row > .btn {
  flex-grow: 1;
}

@media (max-width: 640px) {
  .bp__lede {
    font-size: 19px;
  }
}
