/*
 * Page furniture for bytesnare.com.
 *
 * Hand-written, unlike tokens.css — this is layout and typography, and there is
 * nothing here a generator would keep honest.
 *
 * Two rules, the same two the product's stylesheets follow:
 *
 *   1. No raw colour, spacing or size values. Everything is a token, so the
 *      site and the endpoint UI are one material rather than two that were
 *      briefed separately.
 *   2. Logical properties only, so a right-to-left layout is this stylesheet
 *      mirrored rather than a second one to maintain.
 *
 * # Fonts
 *
 * Self-hosted `woff2`, served from this origin, as the previous version of this
 * comment said would come with the real site. It has come.
 *
 * The reason it is files here and not a stylesheet link to a font CDN has not
 * changed: a security vendor's own site making a third-party request is an
 * argument losing itself in a footnote, and `website.test.ts` fails the build
 * for one. A page that renders with the network off keeps rendering.
 *
 * What changed is that the previous arrangement did not work. The stacks named
 * DM Sans and Inter and fell through to the system — and neither font is
 * installed on a stock Mac, a stock Windows, or the machine this repository is
 * developed on. So the brand type had never rendered for anybody, including us,
 * and every screenshot taken of this site was of `system-ui` while the token
 * layer recorded a chosen pairing that reached nothing.
 *
 * A font stack naming faces nobody has is a stack with no effect and no error,
 * which is the same shape as the rest of this week: it looked configured.
 *
 * Licence and provenance for both files: `web/_assets/fonts/LICENSE.txt`.
 */

/*
 * `swap`, deliberately. The alternative is a page that renders nothing until a
 * font arrives, and this site's whole argument is that it keeps working when
 * the network does not. A visible fallback that is replaced is better than a
 * blank heading that might not be.
 */
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/_assets/fonts/dm-sans-700-latin.woff2') format('woff2');
  /* Latin only. The subset is what keeps this at 14KB, and it is a limit worth
     stating rather than discovering: a heading in Greek or Cyrillic falls back
     to the system face. §6.3's localization work will need the other subsets,
     and this line is where that shows up. */
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/*
 * One file for every body weight. Inter ships as a variable font, so 400 and
 * 600 are positions on an axis rather than two downloads — which is why there
 * are two files here and not three.
 */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/_assets/fonts/inter-variable-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

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

body {
  overflow-x: clip;
  margin: 0;
  min-block-size: 100vh;
  padding: var(--bs-space-xxl) var(--bs-space-xl);
  background: var(--bs-surface-base);
  color: var(--bs-text-primary);
  font-family: var(--bs-font-body);
  font-size: var(--bs-font-size-base);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

/*
 * Normal document flow rather than a vertically centred card. The single-page
 * holding and status documents looked fine centred; a four-page site with a
 * masthead does not, and one layout that suits both is worth more than two.
 */
main,
.colophon {
  inline-size: 100%;
  max-inline-size: 42rem;
  margin-inline: auto;
}

/*
 * The masthead is wider than the prose it sits above.
 *
 * Five navigation items and a wordmark do not fit a 42rem measure — they wrapped
 * onto a second line, with one link stranded under the others. Prose wants a
 * narrow column and navigation wants a wide one, and there is no reason they
 * have to be the same width.
 */
.masthead {
  inline-size: 100%;
  /* Wider than the landing page's content column, because the bar has to hold
     five labels on one line and the page below it does not. It wrapped at 64rem
     once the navigation took the copywriting document's longer labels, leaving
     one item stranded under the others -- which is what the width was raised to
     avoid the first time. */
  max-inline-size: 72rem;
  margin-inline: auto;
}

/*
 * Off-screen until focused. Hiding it from everyone would remove the only fast
 * path past the navigation for someone tabbing.
 */
.skip {
  position: absolute;
  inset-block-start: -3rem;
  inset-inline-start: var(--bs-space-lg);
  padding: var(--bs-space-sm) var(--bs-space-lg);
  background: var(--bs-surface-raised);
  border: 1px solid var(--bs-border-strong);
  border-radius: var(--bs-radius-md);
}

.skip:focus {
  inset-block-start: var(--bs-space-lg);
}

.masthead {
  display: flex;
  flex-wrap: wrap;
  /* `center`, not `baseline`. The nav items are pills now and a pill aligned on
     its text baseline sits low against a wordmark of a different size. */
  align-items: center;
  justify-content: space-between;
  gap: var(--bs-space-lg) var(--bs-space-xl);
  padding-block: var(--bs-space-lg);
  margin-block-end: var(--bs-space-xl);
  /* No rule underneath. A hairline across the page under a bar that is already
     separated by whitespace is a line doing nothing, and the comparison pages
     have none. */
}

.wordmark {
  font-family: var(--bs-font-display);
  font-size: var(--bs-font-size-lg);
  font-weight: 700;
  margin: 0;
  margin-inline-end: auto;
}

.wordmark a {
  color: var(--bs-text-primary);
  text-decoration: none;
}

.masthead nav ul {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bs-space-xs);
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: var(--bs-font-size-sm);
}

/*
 * Navigation items are not body links.
 *
 * They were rendered with the same underline as a link inside a paragraph,
 * which is correct for prose and wrong here: a row of six underlined phrases
 * reads as a sentence that has been broken up, not as a menu. The underline is
 * what tells a reader "this is a link *within text*", and in a bar every item
 * is a link, so the signal carries nothing and costs legibility.
 *
 * Replaced with a hover surface, which is the affordance the category uses and
 * the one a pointer actually discovers.
 */
.masthead nav a,
.masthead nav [aria-current='page'] {
  display: block;
  padding: var(--bs-space-xs) var(--bs-space-md);
  border-radius: var(--bs-radius-pill);
  text-decoration: none;
  font-weight: 500;
  color: var(--bs-text-secondary);
}

.masthead nav a:hover {
  color: var(--bs-text-primary);
  background: color-mix(in srgb, var(--bs-focus-ring) 12%, transparent);
}

/*
 * Focus is not hover. A keyboard user gets the ring from the global rule; this
 * only makes sure the pill under it is filled too, so the focused item reads as
 * the same object a pointer would be over.
 */
.masthead nav a:focus-visible {
  color: var(--bs-text-primary);
}

/*
 * The current page is a span, not a link: a link to where you already are is a
 * keyboard stop that does nothing, and `aria-current` alone leaves it focusable.
 *
 * Marked by a filled pill rather than an underline. Under forced colours the
 * fill is overridden, so the weight below is what survives -- `aria-current` is
 * doing the real work for anybody not looking at the colour.
 */
.masthead [aria-current='page'] {
  color: var(--bs-text-primary);
  font-weight: 700;
  background: color-mix(in srgb, var(--bs-focus-ring) 18%, transparent);
}

/*
 * The wordmark, given the display face and a mark beside it.
 *
 * It was set in the body face at body weight, so the one piece of type on every
 * page that is supposed to be the brand was the piece least distinguishable
 * from a paragraph.
 */
.wordmark {
  font-family: var(--bs-font-display);
  font-size: var(--bs-font-size-lg);
  font-weight: 700;
  letter-spacing: -0.01em;
}

/*
 * The flex lives on the anchor, not the paragraph, so the mark is inside the
 * click target rather than beside it -- a logo you cannot click is a logo that
 * looks broken to anyone who tries.
 */
.wordmark a {
  display: inline-flex;
  align-items: center;
  gap: var(--bs-space-sm);
  text-decoration: none;
  color: var(--bs-text-primary);
}

/*
 * The same snare the application icon draws: two brackets closing on a gap they
 * do not cross. Redrawn as a path rather than shared as a file, because the icon
 * is generated as PNG for Windows and this needs to be a vector that inherits a
 * token -- but the geometry is copied from `ui/shell/icons/make-icon.py` so the
 * site and the title bar are one mark rather than two that resemble each other.
 */
.wordmark__mark {
  inline-size: 1.15rem;
  block-size: 1.15rem;
  flex: none;
  color: var(--bs-focus-ring);
}

/*
 * The call to action.
 *
 * One filled primary and one outline secondary, which is the pattern every
 * competitor examined in the design record uses without exception — it is the
 * category's grammar and there is no reason to fight it.
 */
.cta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bs-space-md);
  margin-block: var(--bs-space-xl) var(--bs-space-lg);
}

.button {
  display: inline-block;
  font-weight: 600;
  text-decoration: none;
  padding: var(--bs-space-md) var(--bs-space-xl);
  border: 1px solid var(--bs-border-strong);
  border-radius: var(--bs-radius-pill);
  color: var(--bs-text-primary);
  /* Sits comfortably beside its neighbours in the download row. */
  text-align: center;
}

/*
 * The primary action is green.
 *
 * It used to be `--bs-focus-ring`, which is a focus colour doing duty as a
 * button background — two meanings for one value, and the reason the site's
 * call to action was blue while the desktop application's was green. It now
 * takes `--bs-action-primary`, the token that exists for exactly this and is
 * already green in both themes, so the site and the application agree.
 */
.button--primary {
  background: var(--bs-action-primary);
  border-color: transparent;
  color: var(--bs-action-ink);
}

/*
 * Inside the hero the scrim is dark whichever theme is chosen, so the button
 * takes the on-media pair rather than the page's. A button's contrast comes
 * from what is behind it, not from what the rest of the page is doing — and in
 * the light theme `--bs-action-primary` is the *dark* green, which on a dark
 * photograph is a button that disappears.
 */
/*
 * Specificity, deliberately: `.hero .button.button--primary` outranks the
 * `.hero *` rule above, which would otherwise paint this label the same light
 * colour as the rest of the hero — white on light green, which measures about
 * 1.9:1 and is unreadable. A blanket descendant rule and a component's own
 * colours are always going to argue; the component has to win, and it has to
 * win by construction rather than by being written further down the file.
 */
.hero .button.button--primary,
.hero .button.button--primary * {
  background: var(--bs-on-media-action);
  color: var(--bs-on-media-action-ink);
}

.hero .button.button--primary * {
  background: transparent;
}

/*
 * A primary action that cannot be taken yet.
 *
 * Still a link, still keyboard-reachable, and it goes somewhere that explains
 * itself — a disabled button that does nothing when clicked teaches a visitor
 * the page is broken. The stripe is a second carrier so the state does not rest
 * on the colour change alone, which is the same rule the product's status marks
 * follow.
 */
.is-unavailable {
  /*
   * The stripe is derived from a token rather than written as a literal. The
   * first version used `rgb(255 255 255 / 18%)`, which the site's colour gate
   * did not catch because it matched only hex — so the gate now matches
   * functional notations too, and this uses `color-mix` so a theme change
   * reaches it.
   */
  background-image: repeating-linear-gradient(
    135deg,
    transparent,
    transparent 6px,
    color-mix(in srgb, var(--bs-text-inverse) 18%, transparent) 6px,
    color-mix(in srgb, var(--bs-text-inverse) 18%, transparent) 12px
  );
}

@media (forced-colors: active) {
  .button {
    border-color: ButtonText;
  }
}

figure {
  margin: var(--bs-space-xl) 0;
}

figure img {
  display: block;
  inline-size: 100%;
  block-size: auto;
  border: 1px solid var(--bs-border-subtle);
  border-radius: var(--bs-radius-md);
}

figcaption {
  margin-block-start: var(--bs-space-sm);
  color: var(--bs-text-muted);
  font-size: var(--bs-font-size-sm);
}

ul {
  padding-inline-start: var(--bs-space-xl);
}

li {
  margin-block-end: var(--bs-space-sm);
}

/*
 * The download block.
 *
 * Three platforms side by side, each with its own state, which is how every
 * comparable product presents a download and also the only honest way to present
 * ours — one button saying "Download" over three platforms in three different
 * conditions would have to pick one and mislead about two.
 */
.download {
  margin-block: var(--bs-space-xxl);
  padding: var(--bs-space-xl);
  background: var(--bs-surface-raised);
  border: 1px solid var(--bs-border-subtle);
  border-radius: var(--bs-radius-lg);
}

.download__title {
  font-family: var(--bs-font-display);
  font-size: var(--bs-font-size-lg);
  font-weight: 700;
  margin-block: 0 var(--bs-space-lg);
}

.download__row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--bs-space-lg);
  list-style: none;
  margin: 0;
  padding: 0;
  max-inline-size: none;
}

.download__item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--bs-space-sm);
  margin: 0;
}

.download__os {
  font-weight: 600;
}

.download__state {
  font-size: var(--bs-font-size-sm);
  color: var(--bs-text-muted);
}

.download__note {
  margin-block: var(--bs-space-lg) 0;
  font-size: var(--bs-font-size-sm);
  color: var(--bs-text-secondary);
}

/*
 * A product screenshot is the one thing on these pages that earns more width
 * than the prose measure — it is a picture of an application window, and shrunk
 * to a reading column it stops being legible.
 */
.figure--wide {
  inline-size: min(58rem, calc(100vw - 3rem));
  margin-inline: calc(50% - min(29rem, calc(50vw - 1.5rem)));
}

.lede {
  font-size: var(--bs-font-size-lg);
  line-height: 1.5;
  color: var(--bs-text-primary);
  margin-block-end: var(--bs-space-xxl);
}

.colophon {
  margin-block-start: var(--bs-space-xxl);
  padding-block-start: var(--bs-space-lg);
  border-block-start: 1px solid var(--bs-border-subtle);
}

h1 {
  font-family: var(--bs-font-display);
  font-size: var(--bs-font-size-xxl);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  margin-block: 0 var(--bs-space-xs);
}

h2 {
  font-family: var(--bs-font-display);
  font-size: var(--bs-font-size-xl);
  font-weight: 700;
  line-height: 1.25;
  margin-block: var(--bs-space-xxl) var(--bs-space-md);
}

h3 {
  font-family: var(--bs-font-display);
  font-size: var(--bs-font-size-lg);
  font-weight: 700;
  margin-block: var(--bs-space-xl) var(--bs-space-sm);
}

.tagline {
  color: var(--bs-text-secondary);
  font-size: var(--bs-font-size-base);
  margin-block: 0 var(--bs-space-xxl);
}

p {
  margin-block: 0 var(--bs-space-lg);
}

/*
 * The reading measure, scoped instead of global.
 *
 * It was on every `p` and `ul` on the site, with layout blocks exempted one at a
 * time as each was caught. That list grew four times: the hero, the variant
 * capability rows, the action block on two variants, and finally the *navigation
 * bar* — five labels in a `ul` capped at 62ch, wrapping one item onto a second
 * line under the others.
 *
 * The comment on the third instance said that if it happened a fourth time the
 * fix was to invert the default. It has, so it is inverted. The measure now
 * applies where prose lives — the reading pages, which are every `main` that has
 * not opted into a layout width — and nowhere else. The exemption list is gone,
 * and so is the class of bug where a centred block sits left of centre because a
 * rule written for paragraphs quietly narrowed it.
 *
 * The navigation was never inside `main` at all, which is why scoping fixes it
 * without naming it: a rule for reading columns had been reaching a menu bar.
 */
main:not(.page--wide) p,
main:not(.page--wide) ul {
  max-inline-size: 62ch;
}

hr {
  border: 0;
  border-block-start: 1px solid var(--bs-border-subtle);
  margin-block: var(--bs-space-xxl);
}

a {
  color: var(--bs-focus-ring);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

/* Never removed, only restyled, and offset so it stays visible on any surface. */
a:focus-visible {
  outline: 2px solid var(--bs-focus-ring);
  outline-offset: 2px;
  border-radius: var(--bs-radius-sm);
}

.note {
  color: var(--bs-text-muted);
  font-size: var(--bs-font-size-sm);
  line-height: 1.6;
}

/*
 * Status marks reuse the product's vocabulary rather than inventing a second
 * one. Shape as well as colour, for the same reason the endpoint UI does it:
 * under forced colours the palette is overridden and hue stops carrying meaning.
 */
.mark {
  display: inline-flex;
  align-items: center;
  gap: var(--bs-space-sm);
}

.mark::before {
  content: '';
  inline-size: 0.7em;
  block-size: 0.7em;
  flex: none;
  background: currentColor;
}

.mark--ok {
  color: var(--bs-status-active);
}

.mark--ok::before {
  border-radius: 50%;
}

.mark--pending {
  color: var(--bs-status-enabled);
}

.mark--pending::before {
  border-radius: 50%;
  background: none;
  border: 2px solid currentColor;
}

.mark--none {
  color: var(--bs-status-unsupported);
}

.mark--none::before {
  block-size: 2px;
}

table {
  inline-size: 100%;
  border-collapse: collapse;
  font-size: var(--bs-font-size-sm);
}

th,
td {
  text-align: start;
  padding-block: var(--bs-space-sm);
  border-block-end: 1px solid var(--bs-border-subtle);
}

th {
  color: var(--bs-text-secondary);
  font-weight: 600;
}

code {
  font-family: var(--bs-font-mono);
  font-size: 0.9em;
}

pre {
  overflow-x: auto;
  padding: var(--bs-space-lg);
  background: var(--bs-surface-sunken);
  border: 1px solid var(--bs-border-subtle);
  border-radius: var(--bs-radius-md);
  font-size: var(--bs-font-size-sm);
  line-height: 1.5;
}

/*
 * A reference table is the one place on these sites that legitimately exceeds
 * the prose measure, so it scrolls inside its own container rather than making
 * the page scroll sideways.
 */
.table-scroll,
main > table {
  display: block;
  max-inline-size: 100%;
  overflow-x: auto;
}

/* Generated field lists: dense, monospace, and not a bulleted prose list. */
.fields {
  columns: 2;
  column-gap: var(--bs-space-xl);
  list-style: none;
  padding: 0;
  margin-block: 0 var(--bs-space-lg);
  font-family: var(--bs-font-mono);
  font-size: var(--bs-font-size-sm);
}

.fields li {
  break-inside: avoid;
}

@media (max-width: 34rem) {
  .fields {
    columns: 1;
  }
}

/*
 * A page that is a machine-facing origin rather than a document reads in the
 * monospace stack. Applied to the body rather than to each element, so the page
 * carries no inline style and can be served under a policy that forbids them.
 */
.mono {
  font-family: var(--bs-font-mono);
}

.mono h1 {
  font-family: var(--bs-font-mono);
  font-size: var(--bs-font-size-lg);
  letter-spacing: 0;
}


/* ---------------------------------------------------------------------------
 * The landing page.
 *
 * Modelled on the shape the category converges on -- Avast, AVG, Bitdefender
 * and Avira all open with a centred hero, one large action, a tinted band of
 * short capability statements, and a card grid. The repo owner named Avast's as
 * the cleanest of the four and asked for this to look like it.
 *
 * What is taken is the *composition*: centred, generous, one thing at a time,
 * big display type, soft tinted bands to break the page into movements, and
 * rounded cards. That is a good way to lay out a page and there is no reason to
 * be different for its own sake.
 *
 * What is not taken is everything those pages put *in* the composition. No
 * award badges, no review score, no "millions of people choose", no shield, no
 * illustration of a laptop with a tick on it. Not for austerity -- we have none
 * of those things, and a landing page for a product whose entire position is
 * declaring its own limits cannot open with a borrowed trust signal. The band
 * where Avast puts four reassurances holds four statements that are true of
 * this build today, and the card grid holds three properties a reader can go
 * and check.
 *
 * Every value is a token. `color-mix` against a token is how the tinted bands
 * are made: naming a soft peach here would be inventing a colour the product
 * does not have, and `tests/website.test.ts` fails the build for it.
 * ------------------------------------------------------------------------- */

/*
 * The landing page's content column.
 *
 * `main` is 42rem everywhere else, which is a reading measure and the right one
 * for prose. A landing page is layout rather than prose: at 42rem the four-item
 * band folds to two-by-two and the hero reads as a middled paragraph.
 */
.page--wide {
  max-inline-size: 64rem;
}

.hero {
  text-align: center;
  padding-block: var(--bs-space-xxl);
  max-inline-size: 52rem;
  margin-inline: auto;
}

/*
 * The photographic hero.
 *
 * Chosen from five photographic treatments on 26 August 2026, replacing the
 * ambient texture chosen earlier the same day. The photograph is generated —
 * `ui/site/artwork/PROMPT.md` holds the brief — and it is shot from behind the
 * subject's shoulder for a reason worth keeping: two rounds of asking the model
 * for a laptop with no manufacturer logo produced an Apple logo both times, and
 * a camera position that cannot see the lid produced a clean frame first try.
 *
 * # Why this hero is single-theme, on a site that now has a theme switcher
 *
 * A photograph carries its own light. Type over it is legible because of the
 * scrim, not because of the palette, so it stays light whichever theme the
 * visitor has chosen and the scrim guarantees the contrast rather than hoping
 * for it. Every competitor does the same and there is no honest alternative:
 * a hero that inverted with the theme would need two photographs, and the
 * second would be a different picture making a different argument.
 *
 * The rest of the page still follows the switcher. The hero is the exception,
 * and it is an exception because it contains an image rather than because
 * somebody forgot.
 *
 * # Why the type moved left
 *
 * The site's hero centres its text, which over a photograph puts every word in
 * the middle — exactly where a scrim must be darkest to keep them readable — so
 * the picture disappears behind its own protection. Type down one side and the
 * photograph breathing on the other is what all three competitors do and the
 * only arrangement that works.
 */
.hero {
  position: relative;
  isolation: isolate;
  max-inline-size: none;
  text-align: start;
  padding-block: calc(var(--bs-space-xxl) * 1.6);
  padding-inline: var(--bs-space-lg);
}

.hero::before {
  content: "";
  position: absolute;
  z-index: -2;
  inset-block: 0;
  inset-inline-start: 50%;
  inline-size: 100vw;
  transform: translateX(-50%);
  background-image: url("/_assets/artwork/photo-person.jpg");
  background-size: cover;
  background-position: center right;
  background-repeat: no-repeat;
}

.hero::after {
  content: "";
  position: absolute;
  z-index: -1;
  inset-block: 0;
  inset-inline-start: 50%;
  inline-size: 100vw;
  transform: translateX(-50%);
  background: linear-gradient(
    90deg,
    rgba(var(--bs-on-media-scrim-rgb), 0.92) 0%,
    rgba(var(--bs-on-media-scrim-rgb), 0.86) 45%,
    rgba(var(--bs-on-media-scrim-rgb), 0.72) 62%,
    rgba(var(--bs-on-media-scrim-rgb), 0.28) 82%,
    rgba(var(--bs-on-media-scrim-rgb), 0.10) 100%
  );
}

.hero > * {
  position: relative;
  max-inline-size: 34rem;
  margin-inline: 0 auto;
}

.hero__actions {
  justify-content: flex-start;
}

/*
 * Everything in the hero takes the on-media colour, by descent rather than by
 * an enumerated list.
 *
 * The list was `.hero__eyebrow, .hero__title, .hero__lede, .hero__actions,
 * .hero__note, .hero p` and it missed `.hero__state` and `.hero__free` — two
 * spans inside the actions paragraph, which rendered dark grey on a dark
 * photograph and were completely unreadable. One of them is the line that says
 * the build is not signed.
 *
 * A list of class names is a check whose scope is fixed the moment it is
 * written, and the markup kept growing. `.hero *` cannot fall behind.
 *
 * The exception list is now the thing with the fixed scope, and it fell behind
 * immediately: `:not(.hero__brush)` excludes the SVG element and not one thing
 * inside it, while `.hero *` matches every descendant -- so the `<path
 * fill="currentColor">` took the text colour and no rule written on the SVG
 * could reach it. Excluding a subtree takes both halves.
 */
.hero,
.hero *:not(.hero__brush, .hero__brush *) {
  color: var(--bs-on-media-text);
}

/*
 * The brush keeps its own colour. It is an SVG filled with `currentColor`, so
 * the blanket rule above turned the blue swipe behind "free" into a grey one —
 * the mark that carries the single word the page is selling, quietly
 * decolourised by a rule about text.
 */
.hero .hero__brush {
  /* Green, matching the download button rather than the body text. "Free" is
     the word the call to action is about, so the stroke under it and the button
     it leads to are the same colour by intent rather than by coincidence. It
     read grey before, because the blanket hero rule had taken it. */
  color: var(--bs-on-media-action);
}

.hero a:not(.button) {
  color: var(--bs-on-media-link);
}

/*
 * The hero column used to be pushed off centre here, to sit in the page's own
 * measure and leave the right of the photograph clear. It is centred now, so
 * the shift is gone rather than overridden -- an override would have left this
 * comment describing a layout the page no longer has.
 *
 * A stray `}` sat directly below it, closing nothing. CSS error recovery skips
 * a top-level closing brace silently, so it cost nothing and was invisible in
 * every rendering; it only showed up counting braces before editing next to it.
 */

.hero__eyebrow {
  margin-block-end: var(--bs-space-lg);
  font-size: var(--bs-font-size-sm);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--bs-text-secondary);
}

.hero__title {
  font-family: var(--bs-font-display);
  /* Fluid rather than a media query: the headline is the only element on the
     site whose size genuinely wants to track the viewport, and two breakpoints
     would be two places to forget. The lower bound is what a 320px phone gets
     and the upper is where it stops growing on a desktop. */
  font-size: clamp(2.25rem, 5.5vw, 4rem);
  line-height: 1.05;
  letter-spacing: -0.02em;
  margin-block: 0 var(--bs-space-xl);
  text-wrap: balance;
}

/*
 * The brush stroke behind the phrase that carries the claim.
 *
 * Avast swipes a marker behind two words of its headline, and it is doing one
 * useful thing beyond decoration: telling a reader which part of a long headline
 * is the claim. Ours falls on the half that is the whole product.
 *
 * # Why this is an SVG element and not a background image
 *
 * A `background-image: url("data:image/svg+xml,...")` is the obvious way and it
 * cannot work here. The markup inside a data URI has to name its own fill, which
 * is a colour literal -- `website.test.ts` fails the build for one, and rightly,
 * because a colour named there is a colour outside the token layer that no theme
 * can reach. An element inherits `currentColor` instead, so the stroke is the
 * brand blue in both themes because the token says so.
 *
 * Two overlapping paths rather than one: a single shape reads as a rounded
 * rectangle, and the thing that makes a brush stroke look like ink is the edges
 * disagreeing with each other. `preserveAspectRatio="none"` lets it stretch to
 * whatever the phrase measures -- distortion is wrong for an icon and right for
 * a swipe, which has no correct proportion.
 *
 * # Why the phrase is an inline-block
 *
 * The stroke is absolutely positioned against the mark, and an inline box that
 * wraps has more than one line box to be positioned against -- the stroke would
 * span the first and the second would have none. `inline-block` keeps the phrase
 * together and lets it move to its own line when it does not fit, which is the
 * behaviour a reader would expect anyway.
 */
.hero__mark {
  position: relative;
  display: inline-block;
  /* Contains the z-index below. Without it, `-1` escapes into the page's
     stacking context and the stroke paints behind the section rather than
     behind the words -- visible as nothing at all, which is the hardest kind of
     wrong to debug. */
  isolation: isolate;
}

.hero__brush {
  position: absolute;
  z-index: -1;
  /* Wider than the phrase, so the stroke overshoots the first and last letters
     the way a real one would rather than stopping flush with them. */
  inset-inline: -0.14em;
  inline-size: calc(100% + 0.28em);
  /* Low and thick, covering the lower two thirds of the letters.
     The first attempt was a 0.44em band sitting at the baseline, which does not
     read as a swipe behind the words -- it reads as a line struck through them.
     A marker stroke is wider than the letter height it sits under, not thinner
     than it. */
  inset-block-end: 0.02em;
  block-size: 0.6em;
  color: var(--bs-focus-ring);
  /* Ink, not paint. At full strength the stroke competes with the letters it is
     meant to sit behind. */
  opacity: 0.55;
}

@media (forced-colors: active) {
  .hero__brush {
    /* The system palette overrides the fill, and a solid shape behind the
       headline under forced colours is a shape covering the headline. */
    display: none;
  }
}

.hero__lede {
  font-size: var(--bs-font-size-lg);
  line-height: 1.6;
  color: var(--bs-text-secondary);
  max-inline-size: 44rem;
  margin-inline: auto;
  margin-block-end: var(--bs-space-xxl);
}

.hero__actions {
  margin-block-end: var(--bs-space-xxl);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--bs-space-md);
}

/*
 * The state, immediately under the button rather than further down the page.
 *
 * The button is the size and position the category uses for "Free download",
 * and on those pages pressing it downloads an installer. Pressing this one
 * reaches a page explaining why there is nothing to download yet. A reader is
 * owed that before the click rather than after it, which is the same rule the
 * application's primary action follows: a control that looks like it does the
 * category's thing says what it actually does, beside it.
 */
.hero__state {
  font-size: var(--bs-font-size-sm);
  color: var(--bs-text-secondary);
}

/*
 * What "free" means here, spelled out rather than left as a word.
 *
 * Every comparison page says "free" and every one of them means a tier: an
 * account, an upsell, a trial that ends, or a padlock over the half you did not
 * pay for. The word alone has been worn to nothing in this category, so it is
 * followed immediately by the four things it is being used to deny -- each one
 * checkable, and each one something a reader has been trained to expect.
 *
 * It is a statement about today. Nothing is sold and nothing is withheld for
 * money, which is why the application's unavailable areas say "not built"
 * rather than carrying a padlock. If that stops being true, this line is the
 * first thing that has to change, and it is one line in one place for that
 * reason.
 */
/*
 * The platforms the server did not pick.
 *
 * The hero names one platform because Caddy chose it from the `User-Agent`;
 * this is where the other two go, so a visitor whose browser reports something
 * unexpected -- or who is looking for a different machine -- is one link from
 * the whole picture rather than stuck on a page that has silently decided for
 * them.
 */
.hero__others {
  font-size: var(--bs-font-size-sm);
  color: var(--bs-text-secondary);
  margin-block-start: var(--bs-space-lg);
}

.hero__free {
  font-size: var(--bs-font-size-sm);
  font-weight: 600;
  color: var(--bs-text-primary);
}

.button__glyph {
  inline-size: 1.1em;
  block-size: 1.1em;
  /* Optically centred against the cap height rather than the em box. */
  vertical-align: -0.18em;
  margin-inline-end: 0.5em;
}

@media (forced-colors: active) {
  /*
 * What "free" means here, spelled out rather than left as a word.
 *
 * Every comparison page says "free" and every one of them means a tier: an
 * account, an upsell, a trial that ends, or a padlock over the half you did not
 * pay for. The word alone has been worn to nothing in this category, so it is
 * followed immediately by the four things it is being used to deny -- each one
 * checkable, and each one something a reader has been trained to expect.
 *
 * It is a statement about today. Nothing is sold and nothing is withheld for
 * money, which is why the application's unavailable areas say "not built"
 * rather than carrying a padlock. If that stops being true, this line is the
 * first thing that has to change, and it is one line in one place for that
 * reason.
 */
/*
 * The platforms the server did not pick.
 *
 * The hero names one platform because Caddy chose it from the `User-Agent`;
 * this is where the other two go, so a visitor whose browser reports something
 * unexpected -- or who is looking for a different machine -- is one link from
 * the whole picture rather than stuck on a page that has silently decided for
 * them.
 */
.hero__others {
  font-size: var(--bs-font-size-sm);
  color: var(--bs-text-secondary);
  margin-block-start: var(--bs-space-lg);
}

.hero__free {
  font-size: var(--bs-font-size-sm);
  font-weight: 600;
  color: var(--bs-text-primary);
}

.button__glyph {
    display: none;
  }
}

.button--large {
  font-size: var(--bs-font-size-lg);
  padding: var(--bs-space-lg) var(--bs-space-xxl);
}

/*
 * The tinted band.
 *
 * Where the comparison pages put reassurance, this puts four statements that
 * are true of this build. The tint is the brand blue at low alpha over the page
 * ground, so the band lifts without introducing a second colour; on an engine
 * without `color-mix` it degrades to the sunken surface, which is the correct
 * failure -- a flat band.
 */
.band {
  background: var(--bs-surface-sunken);
  background: color-mix(in srgb, var(--bs-focus-ring) 7%, var(--bs-surface-base));
  border: 1px solid var(--bs-border-subtle);
  border-radius: var(--bs-radius-lg);
  padding: var(--bs-space-xxl);
  margin-block: var(--bs-space-xxl);
}

.band__title {
  margin-block: 0 var(--bs-space-xl);
  text-align: center;
  font-family: var(--bs-font-display);
  font-weight: 700;
  font-size: var(--bs-font-size-lg);
}

.band__row {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: var(--bs-space-xl);
}

.band__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--bs-space-md);
  text-align: center;
  font-size: var(--bs-font-size-sm);
  line-height: 1.5;
  color: var(--bs-text-secondary);
}

/*
 * Glyphs are inline SVG rather than an icon package or a font.
 *
 * §4.3's Content-Security-Policy denies by default and this site fetches
 * nothing from another origin, so an icon font and a sprite hosted anywhere are
 * both out. Inline is also the only form that inherits `currentColor`, which is
 * what makes the forced-colors rule below a one-liner.
 *
 * Every one is decoration: each sits beside a full sentence and carries nothing
 * on its own, which is the property that makes it safe to hide it entirely.
 */
.glyph {
  inline-size: 2rem;
  block-size: 2rem;
  flex: none;
  color: var(--bs-focus-ring);
}

.glyph--card {
  inline-size: 2.25rem;
  block-size: 2.25rem;
}

@media (forced-colors: active) {
  .glyph {
    display: none;
  }
}

.feature {
  padding-block: var(--bs-space-xxl);
  text-align: center;
}

.feature__title {
  font-family: var(--bs-font-display);
  font-size: clamp(1.75rem, 3.5vw, 2.5rem);
  line-height: 1.15;
  letter-spacing: -0.01em;
  margin-block: 0 var(--bs-space-lg);
  text-wrap: balance;
}

.feature__lede {
  font-size: var(--bs-font-size-lg);
  line-height: 1.6;
  color: var(--bs-text-secondary);
  max-inline-size: 46rem;
  margin-inline: auto;
}

.feature__after {
  max-inline-size: 46rem;
  margin-inline: auto;
  margin-block-start: var(--bs-space-xxl);
  text-align: start;
}

.cards {
  list-style: none;
  margin-block: var(--bs-space-xxl) 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: var(--bs-space-xl);
}

.card {
  display: flex;
  flex-direction: column;
  gap: var(--bs-space-md);
  padding: var(--bs-space-xxl) var(--bs-space-xl);
  text-align: start;
  background: var(--bs-surface-raised);
  border: 1px solid var(--bs-border-subtle);
  border-radius: var(--bs-radius-lg);
}

.card h3 {
  margin: 0;
  font-family: var(--bs-font-display);
  font-size: var(--bs-font-size-lg);
  line-height: 1.25;
}

.card p {
  margin: 0;
  font-size: var(--bs-font-size-sm);
  line-height: 1.6;
  color: var(--bs-text-secondary);
}

/*
 * The download block, in its new home inside the hero.
 *
 * It was a bordered box in the middle of the page and is now the row directly
 * under the primary action, which is where the comparison pages put "also
 * available for". The difference is what it says: theirs lists platforms you can
 * have, and this one lists three you cannot, with the reason for each.
 */
.hero .download {
  border: 0;
  padding: 0;
  background: none;
  margin-block: 0;
}

.hero .download__title {
  font-size: var(--bs-font-size-sm);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--bs-text-secondary);
}

.hero .download__row {
  /* Flex, not the grid the row uses elsewhere. `auto-fit` spreads two items
     across the columns a three-item row wanted, so the secondary platforms sat
     at the far edges of the page instead of under the button they belong to. */
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--bs-space-xxl);
}

.hero .download__note {
  max-inline-size: 40rem;
  margin-inline: auto;
  text-align: center;
}



.hero__lede,
.feature__lede,
.feature__after,
.hero .download__note {
  /* Restored deliberately, and centred this time: a lede still wants a measure,
     it just wants to be in the middle of the page while it has one. */
  max-inline-size: 46rem;
  margin-inline: auto;
}


/*
 * The appearance control.
 *
 * One icon button rather than three labelled options. The masthead already
 * carries a wordmark and five links; a segmented control with its own borders
 * and three words was a fourth thing competing with all of them, and this
 * occupies the space of a single nav item.
 *
 * Injected by the theme script rather than emitted in the HTML, so a visitor
 * with script disabled sees no control instead of a dead one. It cycles
 * system -> light -> dark, and its accessible name says which is next, because
 * a cycling control that only reveals itself on hover reveals nothing on a
 * touch screen.
 */
.theme {
  appearance: none;
  display: inline-grid;
  place-items: center;
  inline-size: 2.25rem;
  block-size: 2.25rem;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--bs-radius-sm);
  background: transparent;
  color: var(--bs-text-secondary);
  cursor: pointer;
  margin-inline-start: var(--bs-space-sm);
}

.theme svg {
  inline-size: 1.15rem;
  block-size: 1.15rem;
}

.theme:hover {
  color: var(--bs-text-primary);
  background: var(--bs-surface-sunken);
  border-color: var(--bs-border-subtle);
}

.theme:focus-visible {
  outline: 2px solid var(--bs-focus-ring);
  outline-offset: 2px;
}

/*
 * A second call to action, where a reader who has scrolled actually is.
 *
 * The page had exactly one download button, at the very top. Anyone who read
 * the release table or the band had to scroll back up to act, which is a
 * conversion cost paid by precisely the visitors who read the most. AVG repeats
 * theirs immediately before the product shot; this sits in the same place.
 *
 * It carries the visitor's own platform, because `homeBody` is now a function
 * of it — a shared body with a Windows button on the macOS page would undo the
 * server-side platform detection one section lower down.
 */
.feature--cta {
  text-align: center;
}

.cta-repeat {
  margin-block: var(--bs-space-lg) var(--bs-space-sm);
}

.cta-repeat__state {
  font-size: var(--bs-font-size-sm);
  color: var(--bs-text-muted);
  margin-block: 0;
}

/*
 * The product screenshot, framed.
 *
 * It is the most concrete thing on the page — the design analysis calls showing
 * real software "the one genuinely differentiated move" across every competitor
 * looked at — and it was rendering as a bare rectangle. A frame and a ground
 * are what make a screenshot read as an application rather than as an image
 * that happens to be here.
 *
 * The ground is drawn with the token palette rather than a picture, so it costs
 * no bytes and inverts with the theme.
 */
.figure--framed {
  position: relative;
  padding-block: var(--bs-space-xl);
}

.figure--framed::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline: 50%;
  inline-size: min(64rem, calc(100vw - 2rem));
  transform: translateX(-50%);
  border-radius: var(--bs-radius-lg);
  background: linear-gradient(
    155deg,
    var(--bs-surface-sunken) 0%,
    var(--bs-surface-raised) 55%,
    var(--bs-surface-sunken) 100%
  );
  z-index: -1;
}

.figure--framed img {
  border-radius: var(--bs-radius-md);
  border: 1px solid var(--bs-border-subtle);
  box-shadow:
    0 22px 48px rgba(var(--bs-on-media-scrim-rgb), 0.22),
    0 3px 8px rgba(var(--bs-on-media-scrim-rgb), 0.12);
}

/*
 * The masthead stays put.
 *
 * The page is long and both calls to action are in it, so a reader halfway down
 * has no way back to navigation without scrolling. `position: sticky` is the
 * whole of it — no script, no scroll listener, and it works only because
 * `overflow-x: clip` was chosen over `hidden` for the full-bleed hero: `hidden`
 * would have made the body a scroll container and silently killed this.
 *
 * The blur is what keeps it minimal. A solid bar over a photographic hero is a
 * band across the picture; a translucent one with a backdrop filter reads as
 * glass over whatever is beneath it, and needs no second background for the
 * dark and light themes because it borrows both.
 */
.masthead {
  position: sticky;
  inset-block-start: 0;
  z-index: 10;
}

/*
 * The bar's ground is a pseudo-element, not the masthead's own background,
 * because the masthead is 72rem wide and centred: painting it directly gave a
 * floating plaque with the photograph visible either side of it. The pseudo
 * spans the window using the same `50% - 50vw` idiom the full-bleed hero uses.
 *
 * 92% opaque and no saturation boost. The first attempt was 82% with
 * `saturate(1.4)`, and the green stroke behind "free" smeared up through the
 * bar as a coloured haze behind the navigation -- a filter that amplifies what
 * is under it will amplify the one thing on the page that is deliberately loud.
 * The blur stays, so it reads as glass rather than a slab, but it only softens
 * what is behind it now; it no longer competes with it.
 */
.masthead::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset-block: 0;
  inset-inline: calc(50% - 50vw);
  background: color-mix(in srgb, var(--bs-surface-base) 92%, transparent);
  backdrop-filter: blur(14px);
  border-block-end: 1px solid var(--bs-border-subtle);
}

@supports not (backdrop-filter: blur(1px)) {
  /* Without a filter the translucency is just a weak colour, so the bar goes
     solid rather than illegible. */
  .masthead::before {
    background: var(--bs-surface-base);
  }
}

/*
 * A hairline before the appearance control, so it reads as a separate kind of
 * thing from the five links rather than a sixth one.
 */
/*
 * A hairline before the appearance control, so it reads as a different kind of
 * thing from the five links rather than a sixth one.
 *
 * It is a pseudo-element in the gap, not a border on the button. A border was
 * the first attempt and it forced the two left corners square to stop the
 * hairline curving -- invisible while the button is transparent, and obviously
 * wrong the moment a hover fills it, which is a half-pill. The divider belongs
 * to the space between the two groups, not to either one.
 */
.theme {
  position: relative;
  margin-inline-start: var(--bs-space-lg);
}

.theme::before {
  content: "";
  position: absolute;
  inset-inline-start: calc(-1 * var(--bs-space-md));
  inset-block: 22%;
  inline-size: 1px;
  background: var(--bs-border-subtle);
}

/*
 * The current section is stated by weight and a hairline under it, not by a
 * filled pill. A pill on the active item and a pill on hover are the same
 * shape doing two jobs, so a visitor cannot tell where they are from where
 * they might go.
 */
.masthead nav [aria-current='page'] {
  background: transparent;
  color: var(--bs-text-primary);
  font-weight: 600;
  position: relative;
}

.masthead nav [aria-current='page']::after {
  content: "";
  position: absolute;
  inset-inline: var(--bs-space-md);
  inset-block-end: 0.1rem;
  block-size: 2px;
  border-radius: 2px;
  background: var(--bs-focus-ring);
}

/*
 * No gap between the bar and a full-bleed hero.
 *
 * The masthead's bottom margin is right for a document page, where prose starts
 * underneath it, and wrong for the landing page, where a photograph does: it
 * left a 24px strip of page ground between the hairline and the image. In dark
 * theme that strip is invisible; in light it is a white seam across the top of
 * the photo, which is the sort of defect a single-theme check never sees.
 *
 * The condition is the hero's presence, not the page's name, so a second page
 * that opens with one gets the same treatment without anyone remembering to
 * add it here.
 */
.masthead:has(+ main > .hero:first-child) {
  margin-block-end: 0;
}

/*
 * Three separate reasons the bar was not vertically centred, all of which
 * looked like one problem.
 *
 * 1. The bar started 40px down the window. `body` has `padding: var(--bs-space-xxl)
 *    var(--bs-space-xl)`, so the masthead's box -- and therefore its ground --
 *    began below that padding. The body's background above it is the same
 *    colour as a 92%-opaque bar, so a reader sees one continuous band from the
 *    top of the window to the hairline, and the content sits half the body
 *    padding below that band's centre. The bar was centred perfectly within
 *    itself and wrong in the only thing anyone looks at. A sticky bar pinned to
 *    `inset-block-start: 0` should reach the top of the window, so it now
 *    climbs out of the padding it never wanted; it is already at its stuck
 *    position at rest, so nothing shifts on the first scroll.
 */
.masthead {
  margin-block-start: calc(-1 * var(--bs-space-xxl));
}

/*
 * 2. `li { margin-block-end: var(--bs-space-sm) }` is a vertical-rhythm rule for
 *    prose lists, and the navigation is a list. The masthead reset `margin` on
 *    the `ul` and stopped there, so every item still carried 8px of dead space
 *    underneath it: the `ul` measured 8px taller than the items inside it and
 *    centring the `ul` put the items 4px above the line. Resetting a container
 *    is not resetting its contents -- the same half-a-fix as excluding an
 *    element from a rule without excluding what is inside it.
 */
.masthead nav li {
  margin-block: 0;
}

/*
 * 3. The wordmark is a `<p>` holding an `inline-flex` anchor sitting on the
 *    text baseline, so the line box reserved descender space *below* the
 *    anchor and the paragraph measured 3px taller than its own contents, with
 *    the mark and the word flush to the top of it. Making the paragraph a flex
 *    container removes the line box, and with it the asymmetry: there is no
 *    baseline to sit on and no descender to reserve for.
 */
.wordmark {
  display: flex;
  align-items: center;
}

/*
 * The bar lets go on a narrow screen.
 *
 * Sticky is new, and wrapping is not: below a certain width the five labels
 * drop to a second row and the bar doubles in height. Static that was merely
 * ugly; stuck, a double-height bar follows the reader down a phone screen and
 * takes a fifth of it.
 *
 * The wrap threshold measures 737px today (binary-searched on the real
 * stylesheet, not estimated), and the breakpoint is deliberately set far above
 * it rather than just above it. A constant that sits one pixel from the value
 * it was measured against is the defect this codebase keeps finding: the
 * measurement is fixed the day it is taken and the labels keep growing -- this
 * navigation has already wrapped once when the copywriting document lengthened
 * them. At 64rem the labels would have to grow by two fifths before the guard
 * could fail, and a bar that does not follow you at tablet width costs nothing.
 *
 * `relative`, not `static`: the ground is an absolutely positioned pseudo
 * element, and removing the containing block would send it to the nearest
 * positioned ancestor and stretch it over the page.
 */
@media (max-width: 64rem) {
  .masthead {
    position: relative;
  }
}

/*
 * The headline is the page, so it gets the middle of it and more size.
 *
 * Centred, with a measure of its own: without one the balanced headline would
 * stretch toward the window edges on a wide screen and stop being a shape.
 * `margin-inline: auto` centres the column, and the photograph is unaffected --
 * both hero pseudo-elements are already 100vw hung off their own centre, which
 * stays the window's centre while the column is centred in it.
 */
.hero {
  text-align: center;
  max-inline-size: 58rem;
  margin-inline: auto;
}

.hero__title {
  /* Up from `clamp(2.25rem, 5.5vw, 4rem)`. Still fluid rather than stepped, for
     the reason the original gave: two breakpoints would be two places to
     forget. The cap is what a desktop sees and is the size that was asked for;
     the floor moves far less, because a phone is bounded by the longest word
     and not by taste. */
  font-size: clamp(2.5rem, 6.5vw, 5rem);
}

/*
 * Centring the hero column was not enough on its own: `.hero > *` carried
 * `margin-inline: 0 auto` and `.hero__actions` `justify-content: flex-start`,
 * both correct for a headline set against the left of the photograph and both
 * invisible until the column moved. Setting `text-align: center` on the parent
 * looked like it had worked, because the lede has an auto margin of its own and
 * the lede is what the eye checks first.
 */
.hero > * {
  margin-inline: auto;
}

.hero__actions {
  justify-content: center;
}

.hero__title {
  /* Wider than the 34rem the other hero children get. At this size the measure
     is not what keeps the headline readable -- `text-wrap: balance` is -- and
     34rem forced "malware" onto a line by itself. */
  max-inline-size: 50rem;
}

/*
 * More room above and below the hero text.
 *
 * Was `calc(var(--bs-space-xxl) * 1.6)` -- a flat 64px at every width, which
 * was already tight at 4rem type and became crowded at 5rem. Fluid now, so the
 * space grows with the headline that caused the problem rather than being set
 * once for whatever size the headline happened to be that week.
 *
 * The floor is the old value exactly, so a phone is unchanged: at 375px the
 * headline is four lines and the band is tall enough already, and the download
 * button is the thing that must stay reachable.
 */
.hero {
  padding-block: clamp(4rem, 8vw, 8rem);
}
